Skip to content

Commit a0345ae

Browse files
authored
Delegating depiction to Zod 4 (#2547)
Exploring the possibility to delegate OpenAPI compatible depiction to JSON Schema based method of Zod 4. Current issues: - Can not depict `BigInt` with throwing error if it's within `ZodLiteral` - colinhacks/zod#4220 - Numeric limits are wrong: - exclusive, while should be inclusive - missing at all (`z.number().int().positive()`) - colinhacks/zod#4074 (comment) - suggested fix colinhacks/zod#4224 - Overrides act after default depiction, not before, so that much has to be rewritten
1 parent 16d8501 commit a0345ae

File tree

12 files changed

+723
-1321
lines changed

12 files changed

+723
-1321
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
- `IOSchema` type had to be simplified down to a schema resulting in a `object`, but not an `array`;
1111
- Despite supporting examples by the new Zod method `.meta()`, users should still use `.example()` to set them;
1212
- Refer to [Migration guide on Zod 4](https://v4.zod.dev/v4/changelog) for adjusting your schemas;
13+
- Generating Documentation is partially delegated to Zod 4 `z.toJSONSchema()`:
14+
- The basic depiction of each schema is now natively performed by Zod 4;
15+
- Express Zod API implements some overrides and improvements to fit it into OpenAPI 3.1 that extends JSON Schema;
16+
- The `numericRange` option removed from `Documentation` class constructor argument;
17+
- The `brandHandling` should consist of postprocessing functions altering the depiction made by Zod 4;
18+
- The `Depicter` type changed to `Overrider` having different signature;
1319

1420
## Version 23
1521

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,27 +1388,27 @@ const routing: Routing = {
13881388
You can customize handling rules for your schemas in Documentation and Integration. Use the `.brand()` method on your
13891389
schema to make it special and distinguishable for the framework in runtime. Using symbols is recommended for branding.
13901390
After that utilize the `brandHandling` feature of both constructors to declare your custom implementation. In case you
1391-
need to reuse a handling rule for multiple brands, use the exposed types `Depicter` and `Producer`.
1391+
need to reuse a handling rule for multiple brands, use the exposed types `Overrider` and `Producer`.
13921392

13931393
```ts
13941394
import ts from "typescript";
13951395
import { z } from "zod";
13961396
import {
13971397
Documentation,
13981398
Integration,
1399-
Depicter,
1399+
Overrider,
14001400
Producer,
14011401
} from "express-zod-api";
14021402

14031403
const myBrand = Symbol("MamaToldMeImSpecial"); // I recommend to use symbols for this purpose
14041404
const myBrandedSchema = z.string().brand(myBrand);
14051405

1406-
const ruleForDocs: Depicter = (
1407-
schema: typeof myBrandedSchema, // you should assign type yourself
1408-
{ next, path, method, isResponse }, // handle a nested schema using next()
1406+
const ruleForDocs: Overrider = (
1407+
{ zodSchema, jsonSchema }, // adjust jsonSchema for overrides
1408+
{ path, method, isResponse }, // handle a nested schema using next()
14091409
) => {
1410-
const defaultDepiction = next(schema.unwrap()); // { type: string }
1411-
return { summary: "Special type of data" };
1410+
delete jsonSchema.format;
1411+
jsonSchema.summary = "Special type of data";
14121412
};
14131413

14141414
const ruleForClient: Producer = (

example/example.documentation.yaml

Lines changed: 40 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ paths:
1717
description: a numeric string containing the id of the user
1818
schema:
1919
type: string
20+
format: regex
2021
pattern: \d+
2122
description: a numeric string containing the id of the user
2223
responses:
@@ -28,30 +29,20 @@ paths:
2829
type: object
2930
properties:
3031
status:
31-
type: string
3232
const: success
33+
type: string
3334
data:
3435
type: object
3536
properties:
3637
id:
3738
type: integer
38-
format: int64
39-
minimum: 0
4039
maximum: 9007199254740991
4140
name:
4241
type: string
4342
features:
4443
type: array
4544
items:
46-
type: object
47-
properties:
48-
title:
49-
type: string
50-
features:
51-
$ref: "#/components/schemas/Schema1"
52-
required:
53-
- title
54-
- features
45+
$ref: "#/components/schemas/Schema1"
5546
required:
5647
- id
5748
- name
@@ -67,8 +58,8 @@ paths:
6758
type: object
6859
properties:
6960
status:
70-
type: string
7161
const: error
62+
type: string
7263
error:
7364
type: object
7465
properties:
@@ -97,6 +88,7 @@ paths:
9788
description: numeric string
9889
schema:
9990
type: string
91+
format: regex
10092
pattern: \d+
10193
description: numeric string
10294
responses:
@@ -184,8 +176,8 @@ paths:
184176
type: object
185177
properties:
186178
status:
187-
type: string
188179
const: success
180+
type: string
189181
data:
190182
type: object
191183
properties:
@@ -222,8 +214,8 @@ paths:
222214
type: object
223215
properties:
224216
status:
225-
type: string
226217
const: error
218+
type: string
227219
error:
228220
type: object
229221
properties:
@@ -267,16 +259,14 @@ paths:
267259
type: object
268260
properties:
269261
status:
270-
type: string
271262
const: created
263+
type: string
272264
data:
273265
type: object
274266
properties:
275267
id:
276268
type: integer
277-
format: int64
278-
exclusiveMinimum: 0
279-
maximum: 9007199254740991
269+
exclusiveMaximum: 9007199254740991
280270
required:
281271
- id
282272
required:
@@ -290,16 +280,14 @@ paths:
290280
type: object
291281
properties:
292282
status:
293-
type: string
294283
const: created
284+
type: string
295285
data:
296286
type: object
297287
properties:
298288
id:
299289
type: integer
300-
format: int64
301-
exclusiveMinimum: 0
302-
maximum: 9007199254740991
290+
exclusiveMaximum: 9007199254740991
303291
required:
304292
- id
305293
required:
@@ -313,8 +301,8 @@ paths:
313301
type: object
314302
properties:
315303
status:
316-
type: string
317304
const: error
305+
type: string
318306
reason:
319307
type: string
320308
required:
@@ -328,13 +316,12 @@ paths:
328316
type: object
329317
properties:
330318
status:
331-
type: string
332319
const: exists
320+
type: string
333321
id:
334322
type: integer
335-
format: int64
336-
minimum: -9007199254740991
337-
maximum: 9007199254740991
323+
exclusiveMinimum: -9007199254740991
324+
exclusiveMaximum: 9007199254740991
338325
required:
339326
- status
340327
- id
@@ -346,8 +333,8 @@ paths:
346333
type: object
347334
properties:
348335
status:
349-
type: string
350336
const: error
337+
type: string
351338
reason:
352339
type: string
353340
required:
@@ -402,6 +389,7 @@ paths:
402389
description: GET /v1/avatar/send Parameter
403390
schema:
404391
type: string
392+
format: regex
405393
pattern: \d+
406394
responses:
407395
"200":
@@ -430,6 +418,7 @@ paths:
430418
description: GET /v1/avatar/stream Parameter
431419
schema:
432420
type: string
421+
format: regex
433422
pattern: \d+
434423
responses:
435424
"200":
@@ -464,6 +453,7 @@ paths:
464453
format: binary
465454
required:
466455
- avatar
456+
additionalProperties: {}
467457
required: true
468458
responses:
469459
"200":
@@ -474,17 +464,15 @@ paths:
474464
type: object
475465
properties:
476466
status:
477-
type: string
478467
const: success
468+
type: string
479469
data:
480470
type: object
481471
properties:
482472
name:
483473
type: string
484474
size:
485475
type: integer
486-
format: int64
487-
minimum: 0
488476
maximum: 9007199254740991
489477
mime:
490478
type: string
@@ -513,8 +501,8 @@ paths:
513501
type: object
514502
properties:
515503
status:
516-
type: string
517504
const: error
505+
type: string
518506
error:
519507
type: object
520508
properties:
@@ -553,15 +541,13 @@ paths:
553541
type: object
554542
properties:
555543
status:
556-
type: string
557544
const: success
545+
type: string
558546
data:
559547
type: object
560548
properties:
561549
length:
562550
type: integer
563-
format: int64
564-
minimum: 0
565551
maximum: 9007199254740991
566552
required:
567553
- length
@@ -576,8 +562,8 @@ paths:
576562
type: object
577563
properties:
578564
status:
579-
type: string
580565
const: error
566+
type: string
581567
error:
582568
type: object
583569
properties:
@@ -619,19 +605,15 @@ paths:
619605
properties:
620606
data:
621607
type: integer
622-
format: int64
623-
exclusiveMinimum: 0
624-
maximum: 9007199254740991
608+
exclusiveMaximum: 9007199254740991
625609
event:
626-
type: string
627610
const: time
611+
type: string
628612
id:
629613
type: string
630614
retry:
631615
type: integer
632-
format: int64
633-
exclusiveMinimum: 0
634-
maximum: 9007199254740991
616+
exclusiveMaximum: 9007199254740991
635617
required:
636618
- data
637619
- event
@@ -659,6 +641,7 @@ paths:
659641
email:
660642
type: string
661643
format: email
644+
pattern: ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
662645
message:
663646
type: string
664647
minLength: 1
@@ -676,16 +659,14 @@ paths:
676659
type: object
677660
properties:
678661
status:
679-
type: string
680662
const: success
663+
type: string
681664
data:
682665
type: object
683666
properties:
684667
crc:
685668
type: integer
686-
format: int64
687-
exclusiveMinimum: 0
688-
maximum: 9007199254740991
669+
exclusiveMaximum: 9007199254740991
689670
required:
690671
- crc
691672
required:
@@ -699,8 +680,8 @@ paths:
699680
type: object
700681
properties:
701682
status:
702-
type: string
703683
const: error
684+
type: string
704685
error:
705686
type: object
706687
properties:
@@ -720,17 +701,17 @@ paths:
720701
components:
721702
schemas:
722703
Schema1:
723-
type: array
724-
items:
725-
type: object
726-
properties:
727-
title:
728-
type: string
729-
features:
704+
type: object
705+
properties:
706+
title:
707+
type: string
708+
features:
709+
type: array
710+
items:
730711
$ref: "#/components/schemas/Schema1"
731-
required:
732-
- title
733-
- features
712+
required:
713+
- title
714+
- features
734715
responses: {}
735716
parameters: {}
736717
examples: {}

0 commit comments

Comments
 (0)