Skip to content

Show form errors

Composes: ValidationError + PhoneNumber

This guide turns every ValidationError kind into user-facing copy with one exhaustive switch on the discriminated union. Validity is an accepting state of the automaton, and every ValidationError variant is a named way of not being in one, so the switch below covers everything the engine can report.

import { ensureEngineReady, parsePhoneNumber } from '@telixon/core';
import type { ValidationError } from '@telixon/core';
await ensureEngineReady();
function phoneErrorMessage(error: ValidationError): string {
switch (error.kind) {
case 'EMPTY':
return 'Enter a phone number.';
case 'INVALID_CALLING_CODE':
return 'The country code is not recognized. Check the digits after the +.';
case 'TOO_SHORT':
return `The number is too short. Enter at least ${error.minLength} digits.`;
case 'TOO_LONG':
return `The number is too long. Enter at most ${error.maxLength} digits.`;
case 'INVALID_LENGTH':
return `The number has an impossible length. Valid lengths here: ${error.possibleLengths.join(' or ')} digits.`;
case 'POSSIBLE_LOCAL_ONLY':
return 'The number looks local. Include the area code.';
case 'PATTERN_MISMATCH':
return 'The number does not match any pattern for this country.';
case 'NATIONAL_PREFIX_MISSING':
return `Add the leading ${error.expectedPrefix} for domestic format.`;
}
}
const phone = parsePhoneNumber('415 555', { defaultRegion: 'US' });
const error = phone.getValidationError();
console.log(error);
// -> { kind: 'TOO_SHORT', minLength: 10 }
console.log(error ? phoneErrorMessage(error) : null);
// -> The number is too short. Enter at least 10 digits.

Running this prints the structured error and the mapped message shown in the comments. Because ValidationError is a discriminated union and the switch declares a string return type with no default, TypeScript fails the compile if any kind is left unhandled, so a metadata-driven copy gap cannot ship silently.

  1. Initialize the engine once at startup with ensureEngineReady.
  2. Parse the submitted value with parsePhoneNumber. Pass defaultRegion when the input has no leading +.
  3. Query getValidationError() on the returned PhoneNumber. null means there is nothing to show.
  4. Convert the error with phoneErrorMessage, interpolating the payload fields (minLength, maxLength, possibleLengths, expectedPrefix) into the copy.
  5. Decide your NATIONAL_PREFIX_MISSING policy. It is the only kind reported while isValid() returns true, so surface it as a hint rather than a submit blocker. Gating submission itself is covered in Validate on submit.

Every row below is an executed parse. Inputs without a region were parsed with no defaultRegion.

Input Region Error Message shown
'' none { kind: 'EMPTY' } Enter a phone number.
'+999 123 4567' none { kind: 'INVALID_CALLING_CODE' } The country code is not recognized. Check the digits after the +.
'415 555' US { kind: 'TOO_SHORT', minLength: 10 } The number is too short. Enter at least 10 digits.
'(415) 555-01320' US { kind: 'TOO_LONG', maxLength: 10 } The number is too long. Enter at most 10 digits.
'+41 44 668 18 000' none { kind: 'INVALID_LENGTH', possibleLengths: [9, 12] } The number has an impossible length. Valid lengths here: 9 or 12 digits.
'555 0132' US { kind: 'POSSIBLE_LOCAL_ONLY' } The number looks local. Include the area code.
'+1 123 456 7890' none { kind: 'PATTERN_MISMATCH' } The number does not match any pattern for this country.
'7400 123456' GB { kind: 'NATIONAL_PREFIX_MISSING', expectedPrefix: '0' } Add the leading 0 for domestic format.

The Swiss row shows why INVALID_LENGTH is its own kind: the possible lengths for that number are 9 or 12 digits, so the 10-digit input is neither too short nor too long; it sits between them, and possibleLengths carries both values so the copy can name them.

The GB row is the one valid input in the table. isValid() returns true for it; the error tells the user the domestic form of an already-acceptable number. Every other kind implies isValid() is false.

getValidationError() returns the first matching kind in a fixed order, so a value that is wrong in several ways reports exactly one error. Length checks run before pattern checks: an input with an impossible digit count reports TOO_SHORT, TOO_LONG, or INVALID_LENGTH and never reaches PATTERN_MISMATCH. The full precedence contract is specified on ValidationError.