Functional Weave
Code in Rust

validation.lei@1.0.0

impl/typescript.ts

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.
 */
export function 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 };
}