1,935 bytes · the TypeScript implementation · view raw
import { type LeiCheck } from"./validation_lei_types.ts";
function invalid(reason: string): LeiCheck {
return { valid: false, normalised: null, louPrefix: null, reason };
}
function isDigit(ch: string): boolean {
return ch >= "0" && ch <= "9";
}
/** * ISO 7064 MOD 97-10 over the whole LEI, letters read as A = 10 ... Z = 35. * * The expansion is up to 40 digits, past any 64-bit integer, so the remainder * is carried one character at a time. A letter contributes two digits and so * multiplies the running remainder by 100; the largest intermediate is * 96 * 100 + 35. */function mod97(lei: string): number {
let remainder = 0;
for (let i = 0; i < lei.length; i++) {
const code = lei.charCodeAt(i);
remainder = code <= 57 ? (remainder * 10 + (code - 48)) % 97 : (remainder * 100 + (code - 55)) % 97;
}
return remainder;
}
/** * Check a Legal Entity Identifier's format and ISO 17442 check digits. * * Well formed is not issued: only GLEIF says whether an LEI is current. * Validators answer rather than throw. */exportfunction validateLei(value: string): LeiCheck {
if (typeof value !== "string") return invalid("empty");
let lei = "";
for (const ch of value) {
if (ch === " ") continue;
// Folded by hand: toUpperCase() is Unicode-aware and Rust's ASCII fold is not.
lei += ch >= "a" && ch <= "z" ? String.fromCharCode(ch.charCodeAt(0) - 32) : ch;
}
if (lei.length === 0) return invalid("empty");
for (const ch of lei) {
if (!isDigit(ch) && !(ch >= "A" && ch <= "Z")) return invalid("bad-character");
}
if (lei.length !== 20) return invalid("bad-length");
if (!isDigit(lei[18]) || !isDigit(lei[19])) return invalid("bad-format");
// The remainder must be 1, not 0: the check digits are chosen so that it is.if (mod97(lei) !== 1) return invalid("bad-check-digit");
return { valid: true, normalised: lei, louPrefix: lei.slice(0, 4), reason: null };
}