Skip to content

Commit 3a022ba

Browse files
committed
Moving examples the native storage of Zod 4.
1 parent 81724af commit 3a022ba

11 files changed

Lines changed: 74 additions & 75 deletions

example/example.documentation.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ paths:
107107
required: true
108108
description: PATCH /v1/user/:id Parameter
109109
schema:
110-
type: string
111-
minLength: 1
112110
examples:
113111
- "1234567890"
112+
type: string
113+
minLength: 1
114114
examples:
115115
example1:
116116
value: "1234567890"
@@ -133,15 +133,15 @@ paths:
133133
type: object
134134
properties:
135135
key:
136-
type: string
137-
minLength: 1
138136
examples:
139137
- 1234-5678-90
140-
name:
141138
type: string
142139
minLength: 1
140+
name:
143141
examples:
144142
- John Doe
143+
type: string
144+
minLength: 1
145145
birthday:
146146
description: YYYY-MM-DDTHH:mm:ss.sssZ
147147
type: string
@@ -179,9 +179,9 @@ paths:
179179
type: object
180180
properties:
181181
name:
182-
type: string
183182
examples:
184183
- John Doe
184+
type: string
185185
createdAt:
186186
description: YYYY-MM-DDTHH:mm:ss.sssZ
187187
type: string

express-zod-api/src/common-helpers.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { globalRegistry, z } from "zod";
55
import { CommonConfig, InputSource, InputSources } from "./config-type";
66
import { contentTypes } from "./content-type";
77
import { OutputValidationError } from "./errors";
8-
import { metaSymbol } from "./metadata";
98
import { AuxMethod, Method } from "./method";
109

1110
/** @desc this type does not allow props assignment, but it works for reading them when merged with another interface */
@@ -89,8 +88,7 @@ export const getMessageFromError = (error: Error): string => {
8988
export const pullExampleProps = <T extends z.ZodObject>(subject: T) =>
9089
Object.entries(subject.shape).reduce<Partial<z.input<T>>[]>(
9190
(acc, [key, schema]) => {
92-
const examples =
93-
(schema as z.ZodType).meta()?.[metaSymbol]?.examples || [];
91+
const examples = (schema as z.ZodType).meta()?.examples || [];
9492
return combinations(acc, examples.map(R.objOf(key)), ([left, right]) => ({
9593
...left,
9694
...right,
@@ -127,7 +125,7 @@ export const getExamples = <
127125
* */
128126
pullProps?: boolean;
129127
}): ReadonlyArray<V extends "parsed" ? z.output<T> : z.input<T>> => {
130-
let examples = globalRegistry.get(schema)?.[metaSymbol]?.examples || [];
128+
let examples = globalRegistry.get(schema)?.examples || [];
131129
if (!examples.length && pullProps && schema instanceof z.ZodObject)
132130
examples = pullExampleProps(schema);
133131
if (!validate && variant === "original") return examples;

express-zod-api/src/metadata.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import * as R from "ramda";
55
export const metaSymbol = Symbol.for("express-zod-api");
66

77
export interface Metadata {
8-
examples: unknown[];
98
/** @override ZodDefault::_zod.def.defaultValue() in depictDefault */
109
defaultLabel?: string;
1110
brand?: string | number | symbol;
@@ -15,21 +14,22 @@ export const copyMeta = <A extends z.ZodType, B extends z.ZodType>(
1514
src: A,
1615
dest: B,
1716
): B => {
18-
const srcMeta = src.meta()?.[metaSymbol];
19-
const destMeta = dest.meta()?.[metaSymbol];
17+
const srcMeta = src.meta();
18+
const destMeta = dest.meta();
2019
if (!srcMeta) return dest; // ensure metadata in src below
2120
return dest.meta({
2221
description: dest.description,
23-
[metaSymbol]: {
24-
...destMeta,
25-
examples: combinations(
26-
destMeta?.examples || [],
27-
srcMeta.examples || [],
28-
([destExample, srcExample]) =>
29-
typeof destExample === "object" && typeof srcExample === "object"
30-
? R.mergeDeepRight({ ...destExample }, { ...srcExample })
31-
: srcExample, // not supposed to be called on non-object schemas
32-
),
33-
},
22+
examples: combinations(
23+
destMeta?.examples || [],
24+
srcMeta.examples || [],
25+
([destExample, srcExample]) =>
26+
typeof destExample === "object" &&
27+
typeof srcExample === "object" &&
28+
destExample &&
29+
srcExample
30+
? R.mergeDeepRight(destExample, srcExample)
31+
: srcExample, // not supposed to be called on non-object schemas
32+
),
33+
[metaSymbol]: destMeta?.[metaSymbol],
3434
});
3535
};

express-zod-api/src/zod-plugin.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ declare module "@zod/core" {
2323

2424
declare module "zod" {
2525
interface ZodType {
26-
/** @desc Add an example value (before any transformations, can be called multiple times) */
26+
/**
27+
* @todo this should be changed to z.output
28+
* @desc Add an example value (before any transformations, can be called multiple times)
29+
* */
2730
example(example: z.input<this>): this;
2831
deprecated(): this;
2932
}
@@ -55,12 +58,12 @@ declare module "zod" {
5558
}
5659

5760
const exampleSetter = function (this: z.ZodType, value: z.input<typeof this>) {
58-
const { examples, ...rest } = this.meta()?.[metaSymbol] || { examples: [] };
61+
const { examples = [], ...rest } = this.meta() || {};
5962
const copy = examples.slice();
6063
copy.push(value);
6164
return this.meta({
62-
description: this.description,
63-
[metaSymbol]: { ...rest, examples: copy },
65+
examples: copy,
66+
...rest,
6467
});
6568
};
6669

@@ -78,7 +81,7 @@ const labelSetter = function (
7881
) {
7982
return this.meta({
8083
description: this.description,
81-
[metaSymbol]: { examples: [], ...this.meta()?.[metaSymbol], defaultLabel },
84+
[metaSymbol]: { ...this.meta()?.[metaSymbol], defaultLabel },
8285
});
8386
};
8487

@@ -88,7 +91,7 @@ const brandSetter = function (
8891
) {
8992
return this.meta({
9093
description: this.description,
91-
[metaSymbol]: { examples: [], ...this.meta()?.[metaSymbol], brand },
94+
[metaSymbol]: { ...this.meta()?.[metaSymbol], brand },
9295
});
9396
};
9497

@@ -147,9 +150,12 @@ if (!(metaSymbol in globalThis)) {
147150
...args: Parameters<z.ZodType["check"]>
148151
) {
149152
/** @link https://v4.zod.dev/metadata#register */
150-
return originalCheck.apply(this, args).register(globalRegistry, {
151-
[metaSymbol]: this.meta()?.[metaSymbol],
152-
});
153+
return (
154+
originalCheck
155+
.apply(this, args)
156+
// @ts-expect-error -- ignore type because it's unknown
157+
.register(globalRegistry, this.meta() || {})
158+
);
153159
};
154160
},
155161
},

express-zod-api/tests/__snapshots__/documentation.spec.ts.snap

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3172,6 +3172,11 @@ paths:
31723172
status:
31733173
const: success
31743174
data:
3175+
examples:
3176+
- a: first
3177+
b: prefix_first
3178+
- a: second
3179+
b: prefix_second
31753180
type: object
31763181
properties:
31773182
a:
@@ -3181,11 +3186,6 @@ paths:
31813186
required:
31823187
- a
31833188
- b
3184-
examples:
3185-
- a: first
3186-
b: prefix_first
3187-
- a: second
3188-
b: prefix_second
31893189
required:
31903190
- status
31913191
- data
@@ -3361,14 +3361,14 @@ paths:
33613361
status:
33623362
const: success
33633363
data:
3364+
examples:
3365+
- num: 123
33643366
type: object
33653367
properties:
33663368
num:
33673369
type: number
33683370
required:
33693371
- num
3370-
examples:
3371-
- num: 123
33723372
required:
33733373
- status
33743374
- data
@@ -3449,14 +3449,14 @@ paths:
34493449
status:
34503450
const: success
34513451
data:
3452+
examples:
3453+
- numericStr: "123"
34523454
type: object
34533455
properties:
34543456
numericStr:
34553457
type: string
34563458
required:
34573459
- numericStr
3458-
examples:
3459-
- numericStr: "123"
34603460
required:
34613461
- status
34623462
- data
@@ -3543,14 +3543,14 @@ paths:
35433543
status:
35443544
const: success
35453545
data:
3546+
examples:
3547+
- numericStr: "123"
35463548
type: object
35473549
properties:
35483550
numericStr:
35493551
type: string
35503552
required:
35513553
- numericStr
3552-
examples:
3553-
- numericStr: "123"
35543554
required:
35553555
- status
35563556
- data

express-zod-api/tests/__snapshots__/endpoint.spec.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ exports[`Endpoint > .getResponses() > should return the negative responses (read
2121
"application/json",
2222
],
2323
"schema": {
24+
"examples": [
25+
{
26+
"error": {
27+
"message": "Sample error message",
28+
},
29+
"status": "error",
30+
},
31+
],
2432
"properties": {
2533
"error": {
2634
"properties": {

express-zod-api/tests/__snapshots__/endpoints-factory.spec.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ exports[`EndpointsFactory > .build() > Should create an endpoint with refined ob
8585
"type": "object",
8686
},
8787
],
88+
"examples": [],
8889
}
8990
`;
9091

express-zod-api/tests/io-schema.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
extractObjectSchema,
66
getFinalEndpointInputSchema,
77
} from "../src/io-schema";
8-
import { metaSymbol } from "../src/metadata";
98
import { AbstractMiddleware } from "../src/middleware";
109

1110
describe("I/O Schema and related helpers", () => {
@@ -288,7 +287,7 @@ describe("I/O Schema and related helpers", () => {
288287
.object({ five: z.string() })
289288
.example({ five: "some" });
290289
const result = getFinalEndpointInputSchema(middlewares, endpointInput);
291-
expect(result.meta()?.[metaSymbol]?.examples).toEqual([
290+
expect(result.meta()?.examples).toEqual([
292291
{
293292
one: "test",
294293
two: 123,

express-zod-api/tests/metadata.spec.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ describe("Metadata", () => {
1515
const src = z.string().example("some");
1616
const dest = z.number();
1717
const result = copyMeta(src, dest);
18-
expect(result.meta()?.[metaSymbol]).toBeTruthy();
19-
expect(result.meta()?.[metaSymbol]?.examples).toEqual(
20-
src.meta()?.[metaSymbol]?.examples,
21-
);
18+
expect(result.meta()?.examples).toEqual(src.meta()?.examples);
2219
});
2320

2421
test("should merge the meta from src to dest", () => {
@@ -32,8 +29,7 @@ describe("Metadata", () => {
3229
.example({ b: 456 })
3330
.example({ b: 789 });
3431
const result = copyMeta(src, dest);
35-
expect(result.meta()?.[metaSymbol]).toBeTruthy();
36-
expect(result.meta()?.[metaSymbol]?.examples).toEqual([
32+
expect(result.meta()?.examples).toEqual([
3733
{ a: "some", b: 123 },
3834
{ a: "another", b: 123 },
3935
{ a: "some", b: 456 },
@@ -54,8 +50,7 @@ describe("Metadata", () => {
5450
.example({ a: { c: 456 } })
5551
.example({ a: { c: 789 } });
5652
const result = copyMeta(src, dest);
57-
expect(result.meta()?.[metaSymbol]).toBeTruthy();
58-
expect(result.meta()?.[metaSymbol]?.examples).toEqual([
53+
expect(result.meta()?.examples).toEqual([
5954
{ a: { b: "some", c: 123 } },
6055
{ a: { b: "another", c: 123 } },
6156
{ a: { b: "some", c: 456 } },
@@ -71,7 +66,7 @@ describe("Metadata", () => {
7166
.object({ items: z.array(z.string()) })
7267
.example({ items: ["e", "f", "g"] });
7368
const result = copyMeta(src, dest);
74-
expect(result.meta()?.[metaSymbol]?.examples).toEqual(["a", "b"]);
69+
expect(result.meta()?.examples).toEqual(["a", "b"]);
7570
});
7671
});
7772
});

express-zod-api/tests/result-handler.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
ResultHandler,
99
} from "../src";
1010
import { ResultHandlerError } from "../src/errors";
11-
import { metaSymbol } from "../src/metadata";
1211
import { AbstractResultHandler, Result } from "../src/result-handler";
1312
import {
1413
makeLoggerMock,
@@ -197,13 +196,13 @@ describe("ResultHandler", () => {
197196
}),
198197
);
199198
expect(apiResponse).toHaveLength(1);
200-
expect(apiResponse[0].schema.meta()?.[metaSymbol]).toMatchSnapshot();
199+
expect(apiResponse[0].schema.meta()).toMatchSnapshot();
201200
});
202201

203202
test("should generate negative response example", () => {
204203
const apiResponse = subject.getNegativeResponse();
205204
expect(apiResponse).toHaveLength(1);
206-
expect(apiResponse[0].schema.meta()?.[metaSymbol]).toMatchSnapshot();
205+
expect(apiResponse[0].schema.meta()).toMatchSnapshot();
207206
});
208207
});
209208

0 commit comments

Comments
 (0)