diff --git a/integration-tests/zod-error-formatter/literals.test.ts b/integration-tests/zod-error-formatter/literals.test.ts new file mode 100644 index 0000000..65af2b0 --- /dev/null +++ b/integration-tests/zod-error-formatter/literals.test.ts @@ -0,0 +1,14 @@ +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 string literals correctly', () => { + const schema = z.literal('foo'); + const result = safeParse(schema, 'bar'); + + assert.strictEqual(result.success, false); + assert.deepStrictEqual(result.error.issues, [ + 'invalid literal: expected "foo", but got string' + ]); +}); diff --git a/source/zod-error-formatter/format-issue.test.ts b/source/zod-error-formatter/format-issue.test.ts index 7237869..6651ce3 100644 --- a/source/zod-error-formatter/format-issue.test.ts +++ b/source/zod-error-formatter/format-issue.test.ts @@ -22,3 +22,14 @@ test('returns the formatted issue when an invalid_type issue is given', () => { }); assert.strictEqual(formattedIssue, 'at foo: expected nan, but got float'); }); + +test('returns the formatted issue when an invalid_literal issue is given', () => { + const formattedIssue = formatIssue({ + code: 'invalid_literal', + message: '', + expected: 'foo', + received: 'bar', + path: ['foo'] + }); + assert.strictEqual(formattedIssue, 'at foo: invalid literal: expected "foo", but got string'); +}); diff --git a/source/zod-error-formatter/format-issue.ts b/source/zod-error-formatter/format-issue.ts index f052f64..157f281 100644 --- a/source/zod-error-formatter/format-issue.ts +++ b/source/zod-error-formatter/format-issue.ts @@ -1,4 +1,5 @@ import type { ZodIssue, ZodIssueCode } from 'zod'; +import { formatInvalidLiteralIssueMessage } from './issue-specific/invalid-literal.js'; import { formatInvalidTypeIssueMessage } from './issue-specific/invalid-type.js'; import { formatPath, isNonEmptyPath } from './path.js'; @@ -9,7 +10,8 @@ type FormatterMap = { }; const issueCodeToFormatterMap: FormatterMap = { - invalid_type: formatInvalidTypeIssueMessage + invalid_type: formatInvalidTypeIssueMessage, + invalid_literal: formatInvalidLiteralIssueMessage }; export function formatIssue(issue: ZodIssue): string { diff --git a/source/zod-error-formatter/issue-specific/invalid-literal.test.ts b/source/zod-error-formatter/issue-specific/invalid-literal.test.ts new file mode 100644 index 0000000..50205cc --- /dev/null +++ b/source/zod-error-formatter/issue-specific/invalid-literal.test.ts @@ -0,0 +1,47 @@ +import { test } from '@sondr3/minitest'; +import assert from 'node:assert'; +import { formatInvalidLiteralIssueMessage } from './invalid-literal.js'; + +test('formats the issue by using the expected value as is and only the type of the received value', () => { + const message = formatInvalidLiteralIssueMessage({ + code: 'invalid_literal', + path: [], + message: '', + expected: 42, + received: { foo: 'bar' } + }); + assert.strictEqual(message, 'invalid literal: expected 42, but got object'); +}); + +test('wraps the expected value in double quotes when it is a string', () => { + const message = formatInvalidLiteralIssueMessage({ + code: 'invalid_literal', + path: [], + message: '', + expected: 'foo', + received: null + }); + assert.strictEqual(message, 'invalid literal: expected "foo", but got null'); +}); + +test('correctly works with undefined as expected value', () => { + const message = formatInvalidLiteralIssueMessage({ + code: 'invalid_literal', + path: [], + message: '', + expected: undefined, + received: null + }); + assert.strictEqual(message, 'invalid literal: expected undefined, but got null'); +}); + +test('correctly works with bigint as expected value', () => { + const message = formatInvalidLiteralIssueMessage({ + code: 'invalid_literal', + path: [], + message: '', + expected: 9_007_199_254_740_993n, + received: null + }); + assert.strictEqual(message, 'invalid literal: expected 9007199254740993, but got null'); +}); diff --git a/source/zod-error-formatter/issue-specific/invalid-literal.ts b/source/zod-error-formatter/issue-specific/invalid-literal.ts new file mode 100644 index 0000000..1fb2954 --- /dev/null +++ b/source/zod-error-formatter/issue-specific/invalid-literal.ts @@ -0,0 +1,14 @@ +import { getParsedType, type Primitive, type ZodInvalidLiteralIssue } from 'zod'; + +function formatPrimitiveValue(value: Primitive): string { + if (typeof value === 'bigint') { + return value.toString(); + } + return JSON.stringify(value); +} + +export function formatInvalidLiteralIssueMessage(issue: ZodInvalidLiteralIssue): string { + const received = getParsedType(issue.received); + const expected = formatPrimitiveValue(issue.expected as Primitive); + return `invalid literal: expected ${expected}, but got ${received}`; +}