All files / src adapter.ts

100% Statements 11/11
100% Branches 12/12
100% Functions 3/3
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145                                                                                                                                                                              6x                           58084x 58084x 50417x                 7667x 7667x                           296748x 238664x       296748x                   15670x    
/**
 * The core shared by the schema adapters (`/zod`, `/valibot`, `/yup`): one
 * function that decides, with `validate()`, what a schema accepts and what
 * it outputs, so the three libraries follow the same rules and give the same
 * messages. Each adapter only maps the result onto its library's API. Only
 * the adapters import this module; the main entry point does not.
 *
 * - A schema accepts what `validate()` accepts with the same options
 *   (`normalize`, `cifControl`, `rejectPlaceholders`, `allowVatPrefix`,
 *   `locale`, and `types` for the generic schema), and outputs
 *   `validate().normalized`, the canonical form (NORM-1 to NORM-4, NIE-3).
 * - A value that is not accepted gives `validate().error`, whole: the
 *   message in the locale, the error code and the SPEC.md rule (INPUT-1 for a
 *   value that is not a string).
 * - `dni` accepts what `isValidDni` accepts: a DNI (DNI-1) or a K/L/M NIF
 *   (KLM-1). `nie` accepts an NIE and `cif` a NIF of a legal person or
 *   entity, through `types` (POLICY-2), so another valid document is
 *   `UNSUPPORTED_TYPE`.
 * - `vat` accepts a Spanish VAT number: `ES` and a NIF (VAT-1). The prefix is
 *   required, as in `isValidSpanishVat`, and the output keeps it (`ES` and
 *   the normalized NIF).
 *
 * Rule IDs refer to SPEC.md.
 */
 
import type { NifType, NifValidationError, ValidateOptions } from "./types";
import { validate } from "./validate";
 
/**
 * Options of the generic schema (`zNif`, `vNif`, `yNif`): the options of
 * `validate()`.
 * @example
 * import { es } from "nif-dni-nie-cif-validation/locales/es";
 *
 * const options: NifSchemaOptions = { types: ["DNI", "NIE"], locale: es };
 * zNif(options).safeParse("B12345674").success; // false: a CIF is not accepted
 * @example
 * const options: NifSchemaOptions = { rejectPlaceholders: true, cifControl: "lenient" };
 * zNif(options).safeParse("00000000T").success; // false: a placeholder (POLICY-1)
 * @see {@link https://github.com/josegoval/nif-dni-nie-cif-validation/blob/master/SPEC.md#policy-2 SPEC.md#policy-2}
 * @since 2.0.0
 */
export type NifSchemaOptions = ValidateOptions;
 
/**
 * Options of the schemas for one type (DNI, NIE, CIF): the options of
 * `validate()` except `types`, which the schema fixes.
 * @example
 * import { es } from "nif-dni-nie-cif-validation/locales/es";
 *
 * const options: TypedNifSchemaOptions = { locale: es };
 * zNie(options).safeParse("12345678Z").error?.issues[0]?.message;
 * // "AquĆ­ no se admite un DNI."
 * @example
 * const options: TypedNifSchemaOptions = { cifControl: "lenient" };
 * zCif(options).safeParse("G1234567D").success; // true
 * @see {@link https://github.com/josegoval/nif-dni-nie-cif-validation/blob/master/SPEC.md#policy-2 SPEC.md#policy-2}
 * @since 2.0.0
 */
export type TypedNifSchemaOptions = Omit<ValidateOptions, "types">;
 
/**
 * Options of the Spanish VAT schema: the options of `validate()` except
 * `allowVatPrefix`, which the schema always sets (the `ES` prefix is
 * required).
 * @example
 * import { es } from "nif-dni-nie-cif-validation/locales/es";
 *
 * const options: SpanishVatSchemaOptions = { locale: es };
 * zSpanishVat(options).parse("ES 12345678-z"); // "ES12345678Z"
 * @example
 * const options: SpanishVatSchemaOptions = { rejectPlaceholders: true };
 * zSpanishVat(options).safeParse("ES00000000T").success; // false: a placeholder
 * @see {@link https://github.com/josegoval/nif-dni-nie-cif-validation/blob/master/SPEC.md#vat-1 SPEC.md#vat-1}
 * @since 2.0.0
 */
export type SpanishVatSchemaOptions = Omit<ValidateOptions, "allowVatPrefix">;
 
/** The schema a check is for. */
export type NifSchemaKind = "nif" | "dni" | "nie" | "cif" | "vat";
 
/** What a check found: the output to keep, or why the value is refused. */
export type NifCheck =
  | { ok: true; value: string }
  | { ok: false; error: NifValidationError };
 
/** The document types each typed schema accepts (POLICY-2). */
const KIND_TYPES: Record<"dni" | "nie" | "cif", NifType[]> = {
  // isValidDni: a DNI and a K/L/M NIF (DNI-1, KLM-1).
  dni: ["DNI", "NIF_KLM"],
  nie: ["NIE"],
  cif: ["CIF"],
};
 
/**
 * VAT-1: `ES` + NIF. `validate()` with `allowVatPrefix` accepts a NIF with or
 * without the prefix, so the prefix is detected first: without the option,
 * `validate()` rejects a value that has it with `INVALID_FORMAT` and rule
 * VAT-1.
 */
function checkVat(value: unknown, opts: ValidateOptions | undefined): NifCheck {
  const bare = validate(value, { ...opts, allowVatPrefix: false });
  if (bare.error?.rule !== "VAT-1")
    return {
      ok: false,
      // A value without the prefix is refused even if it is a valid NIF: say
      // what a VAT number is, in the locale ("ES followed by a NIF", the
      // VAT-1 length message, which `validate` gives for a bare "ES").
      error: (bare.valid
        ? validate("ES", { ...opts, allowVatPrefix: true }).error
        : bare.error) as NifValidationError,
    };
  const result = validate(value, { ...opts, allowVatPrefix: true });
  return result.valid
    ? { ok: true, value: `ES${result.normalized}` }
    : { ok: false, error: result.error as NifValidationError };
}
 
/**
 * Checks `value` for the schema `kind` with `opts`: the normalized output, or
 * the error of `validate()`. Never throws.
 */
export function checkNif(
  value: unknown,
  kind: NifSchemaKind,
  opts?: ValidateOptions
): NifCheck {
  if (kind === "vat") return checkVat(value, opts);
  const result = validate(
    value,
    kind === "nif" ? opts : { ...opts, types: KIND_TYPES[kind] }
  );
  return result.valid
    ? { ok: true, value: result.normalized as string }
    : { ok: false, error: result.error as NifValidationError };
}
 
/**
 * INPUT-1: the message for a value that is not a string, in the locale of
 * `opts`. The schemas of each library check the type themselves, and use it.
 */
export function notAStringMessage(opts?: ValidateOptions): string {
  return (validate(null, opts).error as NifValidationError).message;
}