Skip to content

Commit 2c0036d

Browse files
authored
feat: Custom named schemas in Documentation (#3133)
Related to #1393 (comment) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Documentation generation now supports explicit schema naming via schema metadata (use schema.meta({ id: "UniqueName" })) for stable, meaningful OpenAPI component names. * **Documentation** * Changelog entry added for v26.2.0 describing schema-naming capability. * Example OpenAPI schema renamed to "Feature" in generated docs. * README contributor list updated and usage note added for schema.meta({ id }). * **Tests** * Tests updated to exercise schema metadata and naming behavior. * **Refactor** * Internal documentation naming logic refined for more consistent schema registration. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 351ba7f commit 2c0036d

File tree

8 files changed

+53
-30
lines changed

8 files changed

+53
-30
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Version 26
44

5+
### v26.2.0
6+
7+
- Ability to specify a custom name for a schema in the generated Documentation:
8+
- Use the `.meta()` method on a schema with an `{ id }` as an argument;
9+
- The `id` must be unique across all schemas used in your API;
10+
- The feature proposed by [@arlyon](https://github.com/arlyon) 2 years ago, but the
11+
implementation became possible only with Zod 4 and thanks to suggestions
12+
from [@Upsilon-Iridani](https://github.com/Upsilon-Iridani).
13+
514
### v26.1.0
615

716
- Optimization to the memory consumption for your API:

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ Therefore, many basic tasks can be accomplished faster and easier, in particular
8585

8686
These people contributed to the improvement of the framework by reporting bugs, making changes and suggesting ideas:
8787

88+
[<img src="https://github.com/arlyon.png" alt="@arlyon" width="50" />](https://github.com/arlyon)
89+
[<img src="https://github.com/Upsilon-Iridani.png" alt="@Upsilon-Iridani" width="50" />](https://github.com/Upsilon-Iridani)
8890
[<img src="https://github.com/NicolasMahe.png" alt="@NicolasMahe" width="50" />](https://github.com/NicolasMahe)
8991
[<img src="https://github.com/shadone.png" alt="@shadone" width="50" />](https://github.com/shadone)
9092
[<img src="https://github.com/squishykid.png" alt="@squishykid" width="50" />](https://github.com/squishykid)
@@ -116,7 +118,6 @@ These people contributed to the improvement of the framework by reporting bugs,
116118
[<img src="https://github.com/ben-xD.png" alt="@ben-xD" width="50" />](https://github.com/ben-xD)
117119
[<img src="https://github.com/daniel-white.png" alt="@daniel-white" width="50" />](https://github.com/daniel-white)
118120
[<img src="https://github.com/kotsmile.png" alt="@kotsmile" width="50" />](https://github.com/kotsmile)
119-
[<img src="https://github.com/arlyon.png" alt="@arlyon" width="50" />](https://github.com/arlyon)
120121
[<img src="https://github.com/elee1766.png" alt="@elee1766" width="50" />](https://github.com/elee1766)
121122
[<img src="https://github.com/danclaytondev.png" alt="@danclaytondev" width="50" />](https://github.com/danclaytondev)
122123
[<img src="https://github.com/huyhoang160593.png" alt="@huyhoang160593" width="50" />](https://github.com/huyhoang160593)
@@ -1141,7 +1142,8 @@ const exampleEndpoint = defaultEndpointsFactory.build({
11411142
});
11421143
```
11431144

1144-
_See the example of the generated documentation
1145+
You can also use `schema.meta({ id: "UniqueName" })` for custom schema naming.
1146+
_See the complete example of the generated documentation
11451147
[here](https://github.com/RobinTail/express-zod-api/blob/master/example/example.documentation.yaml)_
11461148

11471149
## Tagging the endpoints

example/endpoints/retrieve-user.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import { defaultEndpointsFactory } from "express-zod-api";
55
import { methodProviderMiddleware } from "../middlewares";
66

77
// Demonstrating circular schemas using z.object()
8-
const feature = z.object({
9-
title: z.string(),
10-
get features() {
11-
return z.array(feature).optional();
12-
},
13-
});
8+
const feature = z
9+
.object({
10+
title: z.string(),
11+
get features() {
12+
return z.array(feature).optional();
13+
},
14+
})
15+
// This gives the schema a custom name in the OpenAPI documentation (must be unique across the API)
16+
.meta({ id: "Feature" });
1417

1518
export const retrieveUserEndpoint = defaultEndpointsFactory
1619
.addMiddleware(methodProviderMiddleware)

example/example.documentation.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ paths:
4242
features:
4343
type: array
4444
items:
45-
$ref: "#/components/schemas/Schema1"
45+
$ref: "#/components/schemas/Feature"
4646
required:
4747
- id
4848
- name
@@ -867,15 +867,16 @@ paths:
867867
message: Sample error message
868868
components:
869869
schemas:
870-
Schema1:
870+
Feature:
871+
id: Feature
871872
type: object
872873
properties:
873874
title:
874875
type: string
875876
features:
876877
type: array
877878
items:
878-
$ref: "#/components/schemas/Schema1"
879+
$ref: "#/components/schemas/Feature"
879880
required:
880881
- title
881882
additionalProperties: false

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ import wellKnownHeaders from "./well-known-headers";
5050
interface ReqResCommons {
5151
makeRef: (
5252
key: object | string,
53-
subject: SchemaObject | ReferenceObject,
54-
name?: string,
53+
value: SchemaObject | ReferenceObject,
54+
proposedName?: string,
5555
) => ReferenceObject;
5656
path: string;
5757
method: ClientMethod;
@@ -323,7 +323,7 @@ export const depictRequestParams = ({
323323
? makeRef(
324324
jsonSchema.id || JSON.stringify(jsonSchema),
325325
depicted,
326-
makeCleanId(description, name),
326+
jsonSchema.id || makeCleanId(description, name),
327327
)
328328
: depicted;
329329
return acc.concat({
@@ -382,6 +382,7 @@ const fixReferences = (
382382
entry.$ref = ctx.makeRef(
383383
depiction.id || depiction, // avoiding serialization, because changing $ref
384384
asOAS(depiction),
385+
depiction.id,
385386
).$ref;
386387
}
387388
continue;

express-zod-api/src/documentation.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,19 @@ export class Documentation extends OpenApiBuilder {
9595

9696
#makeRef(
9797
key: object | string,
98-
subject: SchemaObject | ReferenceObject,
99-
name = this.#references.get(key),
98+
value: SchemaObject | ReferenceObject,
99+
proposedName?: string,
100100
): ReferenceObject {
101+
let name = this.#references.get(key); // search in the cache by the given key
101102
if (!name) {
102-
name = `Schema${this.#references.size + 1}`;
103+
let inc = proposedName ? 0 : 1;
104+
do {
105+
name = `${proposedName ?? "Schema"}${inc ? this.#references.size + inc : ""}`;
106+
inc++;
107+
} while (this.rootDoc.components?.schemas?.[name]); // search in existing references for the unique name
103108
this.#references.set(key, name);
104109
}
105-
this.addSchema(name, subject);
110+
this.addSchema(name, value);
106111
return { $ref: `#/components/schemas/${name}` };
107112
}
108113

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3834,7 +3834,7 @@ paths:
38343834
description: An optional cursor string used for pagination. This can be
38353835
retrieved from the \`next\` property of the previous page response.
38363836
schema:
3837-
$ref: "#/components/schemas/HeadHrisEmployeesParameterCursor"
3837+
$ref: "#/components/schemas/GetHrisEmployeesParameterCursor"
38383838
responses:
38393839
"200":
38403840
description: HEAD /hris/employees Positive response
@@ -3878,10 +3878,6 @@ components:
38783878
- status
38793879
- error
38803880
additionalProperties: false
3881-
HeadHrisEmployeesParameterCursor:
3882-
description: An optional cursor string used for pagination. This can be
3883-
retrieved from the \`next\` property of the previous page response.
3884-
type: string
38853881
responses: {}
38863882
parameters: {}
38873883
examples: {}
@@ -4970,11 +4966,7 @@ paths:
49704966
required: true
49714967
description: POST /v1/:name Parameter
49724968
schema:
4973-
anyOf:
4974-
- type: string
4975-
const: John
4976-
- type: string
4977-
const: Jane
4969+
$ref: "#/components/schemas/NameParam"
49784970
requestBody:
49794971
description: POST /v1/:name Request body
49804972
content:
@@ -5035,7 +5027,14 @@ paths:
50355027
error:
50365028
message: Sample error message
50375029
components:
5038-
schemas: {}
5030+
schemas:
5031+
NameParam:
5032+
id: NameParam
5033+
anyOf:
5034+
- type: string
5035+
const: John
5036+
- type: string
5037+
const: Jane
50395038
responses: {}
50405039
parameters: {}
50415040
examples: {}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,10 @@ describe("Documentation", () => {
804804
":name": defaultEndpointsFactory.build({
805805
method: "post",
806806
input: z.object({
807-
name: z.literal("John").or(z.literal("Jane")),
807+
name: z
808+
.literal("John")
809+
.or(z.literal("Jane"))
810+
.meta({ id: "NameParam" }),
808811
other: z.boolean(),
809812
}),
810813
output: z.object({}),

0 commit comments

Comments
 (0)