mcpField
H
handling-validation-errors

handling-validation-errors

github.com/zagginoDevelopment349171 installsUpdated 5d ago
Repository

About handling-validation-errors

A z-schema skill for developers working with validation errors. It explains how to capture, inspect, filter, and transform error details for programmatic handling or form interfaces.

Key features

  1. 01Capture errors in throw or safe mode
  2. 02Walk nested error details
  3. 03Map paths to form fields
  4. 04Filter errors by code
  5. 05Customize error messages

Use cases

  1. 01Displaying schema errors in forms
  2. 02Handling nested anyOf or oneOf errors
  3. 03Showing selected validation errors
  4. 04Stopping validation at the first error

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/zaggino/z-schema --skill handling-validation-errors

After installing, type/handling-validation-errorsin Claude Code to confirm it loaded.

Details

From the project README

Handling Validation Errors in z-schema

z-schema reports validation errors as ValidateError objects containing a .details array of SchemaErrorDetail. This skill covers inspecting, filtering, mapping, and presenting these errors.

Error structure

import { ValidateError } from 'z-schema';
import type { SchemaErrorDetail } from 'z-schema';

ValidateError extends Error:

PropertyTypeDescription
.namestringAlways 'ValidateError'
.messagestringSummary message
.detailsSchemaErrorDetail[]All individual errors

Each SchemaErrorDetail:

FieldTypeDescription
messagestringHuman-readable text, e.g. "Expected type string but found type number"
codestringMachine-readable code, e.g. "INVALID_TYPE"
params(string | number | Array)[]Values filling the message template placeholders
pathstring | Array<string | number>JSON Pointer to the failing value ("#/age" or ["age"])
keywordstring?Schema keyword that caused the error ("type", "required", etc.)
innerSchemaErrorDetail[]?Sub-errors from combinators (anyOf, oneOf, not)
schemaPathArray<string | number>?Path within the schema to the constraint
schemaIdstring?Schema ID if present
titlestring?Schema title if present
descriptionstring?Schema description if present

Capturing errors

Try/catch (default mode)
import ZSchema from 'z-schema';

const validator = ZSchema.create();

try {
  validator.validate(data, schema);
} catch (err) {
  if (err instanceof ValidateError) {
    for (const detail of err.details) {
      console.log(`[${detail.code}] ${detail.path}: ${detail.message}`);
    }
  }
}
Safe mode (no try/catch)
const validator = ZSchema.create();
const { valid, err } = validator.validateSafe(data, schema);

if (!valid && err) {
  for (const detail of err.details) {
    console.log(`[${detail.code}] ${detail.path}: ${detail.message}`);
  }
}

Walking nested errors

Combinators (anyOf, oneOf, not) produce nested inner errors. A recursive walker handles any depth:

function walkErrors(details: SchemaErrorDetail[], depth = 0): void {
  for (const detail of details) {
    const indent = '  '.repeat(depth);
    console.log(`${indent}[${detail.code}] ${detail.path}: ${detail.message}`);
    if (detail.inner) {
      walkErrors(detail.inner, depth + 1);
    }
  }
}

const { valid, err } = validator.validateSafe(data, schema);
if (!valid && err) {
  walkErrors(err.details);
}
Collecting all leaf errors

Flatten the tree to get every concrete error, skipping combinator wrappers:

function collectLeafErrors(details: SchemaErrorDetail[]): SchemaErrorDetail[] {
  const leaves: SchemaErrorDetail[] = [];
  for (const detail of details) {
    if (detail.inner && detail.inner.length > 0) {
      leaves.push(...collectLeafErrors(detail.inner));
    } else {
      leaves.push(detail);
    }
  }
  return leaves;
}

Mapping errors to form fields

Convert JSON Pointer paths to field names for UI form validation:

function pathToFieldName(path: string | Array<string | number>): string {
  if (Array.isArray(path)) {
    return path.join('.');
  }
  // JSON Pointer string: "#/address/city" → "address.city"
  return path.replace(/^#\/?/, '').replace(/\//g, '.');
}

function errorsToFieldMap(details: SchemaErrorDetail[]): Record<string, string[]> {
  const map: Record<string, string[]> = {};
  const leaves = collectLeafErrors(details);
  for (const detail of leaves) {
    const field = pathToFieldName(detail.path) || '_root';
    (map[field] ??= []).push(detail.message);
  }
  return map;
}

// Usage
const { valid, err } = validator.validateSafe(formData, schema);
if (!valid && err) {
  const fieldErrors = errorsToFieldMap(err.details);
  // { "email": ["Expected type string but found type number"],
  //   "age": ["Value 150 is greater than maximum 120"] }
}
Using array paths

Enable reportPathAsArray for easier programmatic access:

const validator = ZSchema.create({ reportPathAsArray: true });
const { valid, err } = validator.validateSafe(data, schema);
// err.details[0].path → ["address", "city"] instead of "#/address/city"

Filtering errors

Per-call filtering

Pass includeErrors or excludeErrors as the third argument:

// Only report type mismatches
validator.validate(data, schema, { includeErrors: ['INVALID_TYPE'] });

// Suppress string-length errors
validator.validate(data, schema, { excludeErrors: ['MIN_LENGTH', 'MAX_LENGTH'] });
Programmatic post-filtering
const { valid, err } = validator.validateSafe(data, schema);
if (!valid && err) {
  const typeErrors = err.details.filter((d) => d.code === 'INVALID_TYPE');
  const requiredErrors = err.details.filter((d) => d.code === 'OBJECT_MISSING_REQUIRED_PROPERTY');
}

Custom error messages

Map error codes to user-friendly messages:

const friendlyMessages: Record<string, (detail: SchemaErrorDetail) => string> = {
  INVALID_TYPE: (d) => `${pathToFieldName(d.path)} must be a ${d.params[0]}`,
  OBJECT_MISSING_REQUIRED_PROPERTY: (d) => `${d.params[0]} is required`,
  MINIMUM: (d) => `${pathToFieldName(d.path)} must be at least ${d.params[1]}`,
  MAXIMUM: (d) => `${pathToFieldName(d.path)} must be at most ${d.params[1]}`,
  MIN_LENGTH: (d) => `${pathToFieldName(d.path)} must be at least ${d.params[1]} characters`,
  MAX_LENGTH: (d) => `${pathToFieldName(d.path)} must be at most ${d.params[1]} characters`,
  PATTERN: (d) => `${pathToFieldName(d.path)} has an invalid format`,
  ENUM_MISMATCH: (d) => `${pathToFieldName(d.path)} must be one of the allowed values`,
  INVALID_FORMAT: (d) => `${pathToFieldName(d.path)} is not a valid ${d.params[0]}`,
  ARRAY_UNEVALUATED_ITEMS: () => `Array contains items not covered by any schema`,
  OBJECT_UNEVALUATED_PROPERTIES: (d) => `${d.params[0]} is not allowed by the schema`,
  COLLECT_EVALUATED_DEPTH_EXCEEDED: (d) => `Schema nesting too deep (max ${d.params[0]})`,
  MAX_RECURSION_DEPTH_EXCEEDED: (d) => `Recursion depth exceeded (max ${d.params[0]})`,
};

function toFriendlyMessage(detail: SchemaErrorDetail): string {
  const fn = friendlyMessages[detail.code];
  return fn ? fn(detail) : detail.message;
}

Stopping at first error

For fail-fast scenarios:

const validator = ZSchema.create({ breakOnFirstError: true });

This reports only the first error encountered, reducing noise during iterative fixing.

Using the keyword field

The keyword field tells you which schema keyword triggered the error — useful for categorizing errors programmatically:

const { valid, err } = validator.validateSafe(data, schema);
if (!valid && err) {
  for (const detail of err.details) {
    switch (detail.keyword) {
      case 'required':
        // handle missing field
        break;
      case 'type':
        // handle type mismatch
        break;
      case 'format':
        // handle format failure
        break;
    }
  }
}

Reference files

For the full error code list with descriptions, see the validating-json-data skill's error-codes reference.

Comments

Markdown supported