forked from Yara724/api
Validation for prices and objection
This commit is contained in:
34
src/utils/unicode-digits.spec.ts
Normal file
34
src/utils/unicode-digits.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
normalizeMoneyAmountString,
|
||||
normalizeUnicodeDigitsDeep,
|
||||
normalizeUnicodeDigitsToEnglish,
|
||||
parseMoneyAmountToman,
|
||||
} from "./unicode-digits";
|
||||
|
||||
describe("unicode-digits", () => {
|
||||
it("converts Persian digits to ASCII", () => {
|
||||
expect(normalizeUnicodeDigitsToEnglish("۱۲۳۴۵۶۷۸۹۰")).toBe("1234567890");
|
||||
});
|
||||
|
||||
it("normalizes nested request bodies", () => {
|
||||
expect(
|
||||
normalizeUnicodeDigitsDeep({
|
||||
partPrice: "۵۰۰۰۰۰۰",
|
||||
nested: [{ total: "۱۰۰" }],
|
||||
}),
|
||||
).toEqual({
|
||||
partPrice: "5000000",
|
||||
nested: [{ total: "100" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("strips grouping separators for money strings", () => {
|
||||
expect(normalizeMoneyAmountString("۵,۰۰۰,۰۰۰")).toBe("5000000");
|
||||
});
|
||||
|
||||
it("parses integer Toman amount strings", () => {
|
||||
expect(parseMoneyAmountToman("۵۳۰۰۰۰۰")).toBe(5300000);
|
||||
expect(parseMoneyAmountToman("1000")).toBe(1000);
|
||||
expect(parseMoneyAmountToman("12.5")).toBeNull();
|
||||
});
|
||||
});
|
||||
54
src/utils/unicode-digits.ts
Normal file
54
src/utils/unicode-digits.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
function unicodeCharToAsciiDigit(ch: string): string {
|
||||
if (ch >= "0" && ch <= "9") return ch;
|
||||
const cp = ch.codePointAt(0)!;
|
||||
if (cp >= 0x06f0 && cp <= 0x06f9) return String(cp - 0x06f0);
|
||||
if (cp >= 0x0660 && cp <= 0x0669) return String(cp - 0x0660);
|
||||
if (cp >= 0x0966 && cp <= 0x096f) return String(cp - 0x0966);
|
||||
if (cp >= 0xff10 && cp <= 0xff19) return String(cp - 0xff10);
|
||||
const folded = ch.normalize("NFKC");
|
||||
if (folded.length === 1 && folded >= "0" && folded <= "9") return folded;
|
||||
return ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert decimal digits in any Unicode script (Persian, Arabic-Indic, etc.)
|
||||
* to ASCII `0-9`. Other characters are left unchanged.
|
||||
*/
|
||||
export function normalizeUnicodeDigitsToEnglish(input: string): string {
|
||||
return Array.from(input).map(unicodeCharToAsciiDigit).join("");
|
||||
}
|
||||
|
||||
/** Strip grouping separators after digit normalization (for money fields). */
|
||||
export function normalizeMoneyAmountString(input: string): string {
|
||||
return normalizeUnicodeDigitsToEnglish(input)
|
||||
.trim()
|
||||
.replace(/[,،\u066C\u060C]/g, "");
|
||||
}
|
||||
|
||||
/** Parse a money string to a whole **Toman** amount, or `null` if invalid. */
|
||||
export function parseMoneyAmountToman(value: unknown): number | null {
|
||||
if (value == null || value === "") return null;
|
||||
const n = normalizeMoneyAmountString(String(value));
|
||||
if (!/^\d+$/.test(n)) return null;
|
||||
const num = Number(n);
|
||||
if (!Number.isFinite(num)) return null;
|
||||
return num;
|
||||
}
|
||||
|
||||
export function normalizeUnicodeDigitsDeep<T>(value: T): T {
|
||||
if (value == null) return value;
|
||||
if (typeof value === "string") {
|
||||
return normalizeUnicodeDigitsToEnglish(value) as T;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => normalizeUnicodeDigitsDeep(item)) as T;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
||||
out[k] = normalizeUnicodeDigitsDeep(v);
|
||||
}
|
||||
return out as T;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
Reference in New Issue
Block a user