Skip to content

Commit

Permalink
Merge pull request #10 from enormora/simple
Browse files Browse the repository at this point in the history
Format issues with simple static messages
  • Loading branch information
lo1tuma authored Mar 22, 2024
2 parents 7def1d8 + a746aeb commit 5f20ae9
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 10 deletions.
24 changes: 24 additions & 0 deletions integration-tests/zod-error-formatter/map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test } from '@sondr3/minitest';
import assert from 'node:assert';
import { z } from 'zod';
import { safeParse } from '../../source/zod-error-formatter/formatter.js';

test('formats messages for invalid keys in map schemas correctly', () => {
const schema = z.map(z.literal('a'), z.string());
const result = safeParse(schema, new Map([['b', 'foo']]));

assert.strictEqual(result.success, false);
assert.deepStrictEqual(result.error.issues, [
'at [0].key: invalid literal: expected "a", but got string'
]);
});

test('formats messages for invalid values in map schemas correctly', () => {
const schema = z.map(z.literal('a'), z.string());
const result = safeParse(schema, new Map([['a', 0]]));

assert.strictEqual(result.success, false);
assert.deepStrictEqual(result.error.issues, [
'at [0].value: expected string, but got number'
]);
});
34 changes: 34 additions & 0 deletions integration-tests/zod-error-formatter/tuple.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { test } from '@sondr3/minitest';
import assert from 'node:assert';
import { z } from 'zod';
import { safeParse } from '../../source/zod-error-formatter/formatter.js';

test('formats messages for invalid items in tuple schemas correctly', () => {
const schema = z.tuple([z.literal('a')]);
const result = safeParse(schema, ['b']);

assert.strictEqual(result.success, false);
assert.deepStrictEqual(result.error.issues, [
'at [0]: invalid literal: expected "a", but got string'
]);
});

test('formats messages for invalid rest values in tuple schemas correctly', () => {
const schema = z.tuple([z.literal('a')]).rest(z.number());
const result = safeParse(schema, ['a', true]);

assert.strictEqual(result.success, false);
assert.deepStrictEqual(result.error.issues, [
'at [1]: expected number, but got boolean'
]);
});

test('formats messages for extra items in fixed tuple schemas correctly', () => {
const schema = z.tuple([z.literal('a')]);
const result = safeParse(schema, ['a', 'b']);

assert.strictEqual(result.success, false);
assert.deepStrictEqual(result.error.issues, [
'array must contain at most 1 element'
]);
});
65 changes: 61 additions & 4 deletions source/zod-error-formatter/format-issue.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { test } from '@sondr3/minitest';
import assert from 'node:assert';
import { ZodError } from 'zod';
import { formatIssue } from './format-issue.js';

test('returns the original message when the issue code doesn’t have a specific formatter and the path is empty', () => {
test('returns just the message when the path is empty', () => {
const formattedIssue = formatIssue({ code: 'custom', message: 'foo', path: [] });
assert.strictEqual(formattedIssue, 'foo');
assert.strictEqual(formattedIssue, 'invalid input');
});

test('returns the original message with path when the issue code doesn’t have a specific formatter', () => {
test('returns the message with path when the path is not empty', () => {
const formattedIssue = formatIssue({ code: 'custom', message: 'bar', path: ['foo'] });
assert.strictEqual(formattedIssue, 'at foo: bar');
assert.strictEqual(formattedIssue, 'at foo: invalid input');
});

test('returns the formatted issue when an invalid_type issue is given', () => {
Expand Down Expand Up @@ -118,3 +119,59 @@ test('returns the formatted issue when an invalid_union issue is given', () => {
});
assert.strictEqual(formattedIssue, 'at foo: invalid value doesn’t match expected union');
});

test('returns the formatted issue when an invalid_arguments issue is given', () => {
const formattedIssue = formatIssue({
code: 'invalid_arguments',
path: ['foo'],
message: '',
argumentsError: new ZodError([])
});
assert.strictEqual(formattedIssue, 'at foo: invalid function arguments');
});

test('returns the formatted issue when an invalid_return_type issue is given', () => {
const formattedIssue = formatIssue({
code: 'invalid_return_type',
path: ['foo'],
message: '',
returnTypeError: new ZodError([])
});
assert.strictEqual(formattedIssue, 'at foo: invalid function return type');
});

test('returns the formatted issue when an invalid_date issue is given', () => {
const formattedIssue = formatIssue({
code: 'invalid_date',
path: ['foo'],
message: ''
});
assert.strictEqual(formattedIssue, 'at foo: invalid date');
});

test('returns the formatted issue when a custom issue is given', () => {
const formattedIssue = formatIssue({
code: 'custom',
path: ['foo'],
message: ''
});
assert.strictEqual(formattedIssue, 'at foo: invalid input');
});

test('returns the formatted issue when an invalid_intersection_types issue is given', () => {
const formattedIssue = formatIssue({
code: 'invalid_intersection_types',
path: ['foo'],
message: ''
});
assert.strictEqual(formattedIssue, 'at foo: intersection results could not be merged');
});

test('returns the formatted issue when an not_finite issue is given', () => {
const formattedIssue = formatIssue({
code: 'not_finite',
path: ['foo'],
message: ''
});
assert.strictEqual(formattedIssue, 'at foo: number must be finite');
});
23 changes: 17 additions & 6 deletions source/zod-error-formatter/format-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ import { formatPath, isNonEmptyPath } from './path.js';
type FormatterForCode<Code extends ZodIssueCode> = (issue: Extract<ZodIssue, { code: Code; }>) => string;

type FormatterMap = {
readonly [Key in ZodIssueCode]?: FormatterForCode<Key>;
readonly [Key in ZodIssueCode]: FormatterForCode<Key>;
};

function formatSimpleMessage(message: string): (issue: ZodIssue) => string {
return () => {
return message;
};
}

const issueCodeToFormatterMap: FormatterMap = {
invalid_type: formatInvalidTypeIssueMessage,
invalid_literal: formatInvalidLiteralIssueMessage,
Expand All @@ -28,15 +34,20 @@ const issueCodeToFormatterMap: FormatterMap = {
invalid_enum_value: formatInvalidEnumValueIssueMessage,
invalid_string: formatInvalidStringIssueMessage,
invalid_union_discriminator: formatInvalidUnionDiscriminatorIssueMessage,
invalid_union: formatInvalidUnionIssueMessage
invalid_union: formatInvalidUnionIssueMessage,
invalid_arguments: formatSimpleMessage('invalid function arguments'),
invalid_return_type: formatSimpleMessage('invalid function return type'),
invalid_date: formatSimpleMessage('invalid date'),
custom: formatSimpleMessage('invalid input'),
invalid_intersection_types: formatSimpleMessage('intersection results could not be merged'),
not_finite: formatSimpleMessage('number must be finite')
};

export function formatIssue(issue: ZodIssue): string {
const { path, code, message: fallbackMessage } = issue;
const { path, code } = issue;

const formatter = issueCodeToFormatterMap[code];
// @ts-expect-error
const message = formatter === undefined ? fallbackMessage : formatter(issue);
const formatter = issueCodeToFormatterMap[code] as (issue: ZodIssue) => string;
const message = formatter(issue);

if (isNonEmptyPath(path)) {
const formattedPath = formatPath(path);
Expand Down

0 comments on commit 5f20ae9

Please sign in to comment.