CodeSampleX

Exemplo

zod 3.24.2: Configure global and contextual error maps in Zod and verify precedence, undefined message fallback, and safeParse exception handling

Amostra verificada para npm zod 3.24.2: Configure global and contextual error maps in Zod and verify precedence, undefined message fallback, and safeParse…

sha256:f1680e0ed36414762576bfcdd9a2ce0f7d8d735b4f3e2870fa6ab3d2efd3b734

Esta rede oferece uma coisa: uma amostra que compila. Ela a executou em um sandbox e guardou o recibo assinado. Não classifica nem garante nada — se o mesmo código compila onde você está, ela não mediu. Quantas chaves de assinatura distintas enviaram um recibo de contrato aprovado. Uma é só o autor; mais de uma significa que outra pessoa também o compilou. Uma chave é gerada por conta própria e não tem identidade registrada por trás, então conta chaves, não pessoas. MIT-0

Evidência de execução

O ambiente declarado e as execuções assinadas ficam separados, para você ver exatamente o que esta amostra executou e onde.

Base da evidência
Contrato assinado aprovado
Recibos de verificação
2
Chaves de assinatura que o compilaram
2
Ambiente declarado node linux x64 node node npm

Ambientes das execuções de verificação

Ambiente Contrato Etapas Execução
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18

Caso

HOW
Objetivo
Configure global and contextual error maps in Zod and verify precedence, undefined message fallback, and safeParse exception handling
Pacotes
Símbolos
  • setErrorMap
  • getErrorMap
  • defaultErrorMap
Ambiente
node
Criado
2026-08-17T10:19:25Z

Contrato

  1. Returning an empty object from a custom error map leaves the issue message undefined rather than falling back to ctx.defaultError, causing error.format() and error.flatten() to produce arrays containing undefined.
  2. Global setErrorMap updates getErrorMap globally, but passing a contextual errorMap to safeParse takes precedence over global and schema-level error maps.
  3. Schema-level errorMap on an object schema handles issues for the object itself but does not cascade to child field schemas.
  4. Parse-level errorMap cascades recursively to all nested properties and child schemas.
  5. Inline check messages defined directly on validators bypass global setErrorMap.
  6. Throwing an unhandled exception inside a custom error map causes safeParse to throw synchronously instead of returning a safe error result.
  7. Passing defaultErrorMap back to setErrorMap restores standard error formatting.

Arquivos

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/config.mjs
  • test/contract.mjs

Baixar o artefato de código-fonte (tar.gz)

Código-fonte

NOTES.md
# Notes on Zod Error Map Configuration, Precedence, and Failure Modes

## Known Solution Search Result
`search_known_solution` returned `MATCH: COMPATIBLE` pointing to existing samples:
- `sha256:30dbed125fd6944057a2d3d053851104df423c6580f301a14ce9d4143dcf07d1` (absent object keys in `.catch()` and `z.preprocess()`)
- `sha256:b1df332fb06be1ad1dddffd68c8ee86edefa3d3c5d234d6ebb92ca96fad55572` (basic object validation and error shapes)
- `sha256:d3a9cc5fa6af1de9c4bd13813f2a670ad60d7afe148dadb0bcc4dc95133a9eb4` (constructor coercion traps in `z.coerce` and `.refine()`)

None of these samples investigate runtime error map configuration via `setErrorMap`, `defaultErrorMap`, `getErrorMap`, precedence resolution across schema and parse scopes, or the failure modes of custom error maps.

## Pinned Release
- `zod@3.24.2`

## Observed Behaviour & Precedence Hierarchy
1. **Empty/Omitted Message in Custom Error Map**:
   When a custom error map returns an empty object `{}` or `{ message: undefined }`, Zod does not fall back to `ctx.defaultError`. Instead, `issue.message` remains `undefined`, causing `error.format()._errors` and `error.flatten().formErrors` to produce arrays containing `undefined` (`[undefined]`).
2. **Global vs Contextual Precedence**:
   Calling `setErrorMap(customMap)` sets the active error map returned by `getErrorMap()`. However, passing `{ errorMap }` into `.parse()` or `.safeParse()` takes strict precedence over global `setErrorMap` and schema-level configurations.
3. **Schema-Level Error Maps Do Not Cascade**:
   A schema-level error map (e.g. `z.object(..., { errorMap })`) only intercepts issues that originate directly on that schema instance (such as passing a non-object to `z.object`). It does not cascade to child properties, which fall back to the global or default error map.
4. **Parse-Level Error Maps Cascade Recursively**:
   Passing `{ errorMap }` at parse time cascades down through all nested child properties, arrays, and sub-schemas.
5. **Inline Check Messages Bypass Error Maps**:
   Validators with explicit custom messages (e.g. `z.string().min(5, { message: '...' })`) bypass global error maps entirely unless overridden at parse time.
6. **Unhandled Exceptions in Error Maps**:
   `safeParse()` catches standard validation failures, but an unhandled exception thrown inside an active `errorMap` propagates out synchronously and crashes callers.
7. **Teardown & Reset**:
   `setErrorMap` mutates global state; restoring default error handling requires passing `defaultErrorMap` explicitly: `setErrorMap(defaultErrorMap)`.

## How the Wrong Expectation Fails
- **Silently with undefined error payloads**: Applications expecting omitted messages in custom error maps to fall back to default English messages instead emit `[undefined]` in JSON error responses and form error structures without throwing any runtime error.
- **Silently missing child errors**: Developers expecting schema-level `z.object(..., { errorMap })` to format child field errors discover child fields continue using default/global formatting.
- **Loudly with unhandled crash in safeParse**: Assuming `safeParse()` is guaranteed never to throw leads to unhandled process crashes if a custom error map throws an unexpected error.
csx.json
{"case":{"believed":"A custom error map returning an empty object falls back to Zod's default error message, and global setErrorMap overrides validation messages across all nested schemas.","caseId":"case:sha256:2f2652783983879401c8ce149136933f024c21ac91272736180ef4524a71177e","contract":["Returning an empty object from a custom error map leaves the issue message undefined rather than falling back to ctx.defaultError, causing error.format() and error.flatten() to produce arrays containing undefined.","Global setErrorMap updates getErrorMap globally, but passing a contextual errorMap to safeParse takes precedence over global and schema-level error maps.","Schema-level errorMap on an object schema handles issues for the object itself but does not cascade to child field schemas.","Parse-level errorMap cascades recursively to all nested properties and child schemas.","Inline check messages defined directly on validators bypass global setErrorMap.","Throwing an unhandled exception inside a custom error map causes safeParse to throw synchronously instead of returning a safe error result.","Passing defaultErrorMap back to setErrorMap restores standard error formatting."],"goal":"Configure global and contextual error maps in Zod and verify precedence, undefined message fallback, and safeParse exception handling","kind":"HOW","packages":["pkg:npm/zod@3.24.2"],"schemaVersion":1,"symbols":["setErrorMap","getErrorMap","defaultErrorMap"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/zod@3.24.2"],"schemaVersion":1,"symbols":["setErrorMap","getErrorMap","defaultErrorMap"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-zod-config",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-zod-config",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "zod": "3.24.2"
      }
    },
    "node_modules/zod": {
      "version": "3.24.2",
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
      "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    }
  }
}
package.json
{
  "name": "npm-zod-config",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "zod": "3.24.2"
  }
}
src/config.mjs
import { z, setErrorMap, defaultErrorMap, getErrorMap } from 'zod';

/**
 * Configure a global custom error map that prefixes validation messages,
 * falling back to ctx.defaultError when no custom override is specified.
 *
 * @param {string} prefix
 */
export function setPrefixedGlobalErrorMap(prefix) {
  setErrorMap((issue, ctx) => {
    if (issue.code === z.ZodIssueCode.invalid_type) {
      return { message: `${prefix}: ${ctx.defaultError}` };
    }
    return { message: ctx.defaultError };
  });
}

/**
 * Restores the standard default error map in Zod.
 */
export function resetGlobalErrorMap() {
  setErrorMap(defaultErrorMap);
}

/**
 * Creates an object schema with a schema-level error map.
 * This demonstrates that schema-level error maps only handle issues
 * produced at the top-level schema instance itself and do not cascade
 * down to child field schemas.
 *
 * @param {string} parentErrorMessage
 */
export function createObjectWithSchemaErrorMap(parentErrorMessage) {
  return z.object(
    {
      name: z.string(),
      age: z.number().int().min(0),
    },
    {
      errorMap: () => ({ message: parentErrorMessage }),
    }
  );
}

/**
 * A custom error map that returns an empty object ({}), illustrating the
 * behavior where omitting the message property leaves issue.message undefined
 * rather than falling back to ctx.defaultError.
 */
export function emptyReturnErrorMap() {
  return {};
}

/**
 * A custom error map that throws an unhandled exception, demonstrating that
 * exceptions inside error maps propagate synchronously out of safeParse().
 *
 * @param {string} message
 */
export function createThrowingErrorMap(message) {
  return () => {
    throw new Error(message);
  };
}
test/contract.mjs
import { strict as assert } from 'node:assert';
import { z, setErrorMap, defaultErrorMap, getErrorMap } from 'zod';
import {
  setPrefixedGlobalErrorMap,
  resetGlobalErrorMap,
  createObjectWithSchemaErrorMap,
  emptyReturnErrorMap,
  createThrowingErrorMap,
} from '../src/config.mjs';

// Ensure we start from a clean default state
resetGlobalErrorMap();
assert.equal(getErrorMap(), defaultErrorMap, 'initial error map must equal defaultErrorMap');

// ---------------------------------------------------------------------------
// 1. Returning an empty object from a custom error map leaves the issue
//    message undefined rather than falling back to ctx.defaultError, causing
//    error.format() and error.flatten() to produce arrays containing undefined.
// ---------------------------------------------------------------------------
setErrorMap(emptyReturnErrorMap);
const emptyMapResult = z.string().safeParse(123);
assert.equal(emptyMapResult.success, false);
assert.equal(
  emptyMapResult.error.issues[0].message,
  undefined,
  'empty error map return leaves issue.message undefined'
);
assert.deepEqual(
  emptyMapResult.error.format()._errors,
  [undefined],
  'error.format()._errors must contain undefined when message is omitted'
);
assert.deepEqual(
  emptyMapResult.error.flatten().formErrors,
  [undefined],
  'error.flatten().formErrors must contain undefined when message is omitted'
);

// ---------------------------------------------------------------------------
// 2. Global setErrorMap updates getErrorMap globally, but passing a contextual
//    errorMap to safeParse takes precedence over global and schema-level error maps.
// ---------------------------------------------------------------------------
setPrefixedGlobalErrorMap('GLOBAL');
assert.notEqual(getErrorMap(), defaultErrorMap);

const globalResult = z.string().safeParse(123);
assert.equal(
  globalResult.error.issues[0].message,
  'GLOBAL: Expected string, received number',
  'global error map formats issues when no contextual map is provided'
);

const contextualParseMap = (issue, ctx) => ({
  message: `CONTEXTUAL[${issue.path.join('.')}]: ${issue.code}`,
});
const contextualResult = z.string().safeParse(123, { errorMap: contextualParseMap });
assert.equal(
  contextualResult.error.issues[0].message,
  'CONTEXTUAL[]: invalid_type',
  'contextual parse-level errorMap must override global error map'
);

// ---------------------------------------------------------------------------
// 3. Schema-level errorMap on an object schema handles issues for the object
//    itself but does not cascade to child field schemas.
// ---------------------------------------------------------------------------
const userSchema = createObjectWithSchemaErrorMap('INVALID_ROOT_OBJECT');

// Failure at the object level (not an object) uses the schema-level error map
const rootFailResult = userSchema.safeParse('not-an-object');
assert.equal(rootFailResult.success, false);
assert.equal(
  rootFailResult.error.issues[0].message,
  'INVALID_ROOT_OBJECT',
  'root non-object failure must use schema-level error map'
);

// Failure at the child property level (invalid name type) falls back to the
// global error map rather than the parent object schema-level error map
const childFailResult = userSchema.safeParse({ name: 456, age: 30 });
assert.equal(childFailResult.success, false);
assert.equal(
  childFailResult.error.issues[0].message,
  'GLOBAL: Expected string, received number',
  'child property failure must fall back to global error map, not parent schema-level map'
);

// ---------------------------------------------------------------------------
// 4. Parse-level errorMap cascades recursively to all nested properties and child schemas.
// ---------------------------------------------------------------------------
const nestedParseResult = userSchema.safeParse(
  { name: 456, age: -5 },
  { errorMap: contextualParseMap }
);
assert.equal(nestedParseResult.success, false);
assert.equal(nestedParseResult.error.issues.length, 2);
assert.equal(
  nestedParseResult.error.issues[0].message,
  'CONTEXTUAL[name]: invalid_type',
  'parse-level errorMap must cascade to nested string property'
);
assert.equal(
  nestedParseResult.error.issues[1].message,
  'CONTEXTUAL[age]: too_small',
  'parse-level errorMap must cascade to nested number property'
);

// ---------------------------------------------------------------------------
// 5. Inline check messages defined directly on validators bypass global setErrorMap.
// ---------------------------------------------------------------------------
const inlineMessageSchema = z.string().min(5, { message: 'EXPLICIT_MIN_MSG' });
const inlineResult = inlineMessageSchema.safeParse('abc');
assert.equal(inlineResult.success, false);
assert.equal(
  inlineResult.error.issues[0].message,
  'EXPLICIT_MIN_MSG',
  'explicit check message must bypass global error map'
);

// ---------------------------------------------------------------------------
// 6. Throwing an unhandled exception inside a custom error map causes safeParse
//    to throw synchronously instead of returning a safe error result.
// ---------------------------------------------------------------------------
setErrorMap(createThrowingErrorMap('FATAL_MAP_ERROR'));
assert.throws(
  () => {
    z.string().safeParse(123);
  },
  {
    name: 'Error',
    message: 'FATAL_MAP_ERROR',
  },
  'unhandled error inside error map must throw synchronously from safeParse'
);

// ---------------------------------------------------------------------------
// 7. Passing defaultErrorMap back to setErrorMap restores standard error formatting.
// ---------------------------------------------------------------------------
resetGlobalErrorMap();
assert.equal(getErrorMap(), defaultErrorMap, 'resetGlobalErrorMap must restore defaultErrorMap');

const restoredResult = z.string().safeParse(123);
assert.equal(restoredResult.success, false);
assert.equal(
  restoredResult.error.issues[0].message,
  'Expected string, received number',
  'restored error map must produce standard default error messages'
);

console.log('CONTRACT PASS: zod error map configuration and precedence contracts verified');

Seeder de origem

csx-seed