Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Version 26

### v26.2.0

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

### v26.1.0

- Optimization to the memory consumption for your API:
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Therefore, many basic tasks can be accomplished faster and easier, in particular

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

[<img src="https://github.com/arlyon.png" alt="@arlyon" width="50" />](https://github.com/arlyon)
[<img src="https://github.com/Upsilon-Iridani.png" alt="@Upsilon-Iridani" width="50" />](https://github.com/Upsilon-Iridani)
[<img src="https://github.com/NicolasMahe.png" alt="@NicolasMahe" width="50" />](https://github.com/NicolasMahe)
[<img src="https://github.com/shadone.png" alt="@shadone" width="50" />](https://github.com/shadone)
[<img src="https://github.com/squishykid.png" alt="@squishykid" width="50" />](https://github.com/squishykid)
Expand Down Expand Up @@ -116,7 +118,6 @@ These people contributed to the improvement of the framework by reporting bugs,
[<img src="https://github.com/ben-xD.png" alt="@ben-xD" width="50" />](https://github.com/ben-xD)
[<img src="https://github.com/daniel-white.png" alt="@daniel-white" width="50" />](https://github.com/daniel-white)
[<img src="https://github.com/kotsmile.png" alt="@kotsmile" width="50" />](https://github.com/kotsmile)
[<img src="https://github.com/arlyon.png" alt="@arlyon" width="50" />](https://github.com/arlyon)
[<img src="https://github.com/elee1766.png" alt="@elee1766" width="50" />](https://github.com/elee1766)
[<img src="https://github.com/danclaytondev.png" alt="@danclaytondev" width="50" />](https://github.com/danclaytondev)
[<img src="https://github.com/huyhoang160593.png" alt="@huyhoang160593" width="50" />](https://github.com/huyhoang160593)
Expand Down Expand Up @@ -1141,7 +1142,8 @@ const exampleEndpoint = defaultEndpointsFactory.build({
});
```

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

## Tagging the endpoints
Expand Down
15 changes: 9 additions & 6 deletions example/endpoints/retrieve-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { defaultEndpointsFactory } from "express-zod-api";
import { methodProviderMiddleware } from "../middlewares";

// Demonstrating circular schemas using z.object()
const feature = z.object({
title: z.string(),
get features() {
return z.array(feature).optional();
},
});
const feature = z
.object({
title: z.string(),
get features() {
return z.array(feature).optional();
},
})
// This gives the schema a custom name in the OpenAPI documentation (must be unique across the API)
.meta({ id: "Feature" });

export const retrieveUserEndpoint = defaultEndpointsFactory
.addMiddleware(methodProviderMiddleware)
Expand Down
7 changes: 4 additions & 3 deletions example/example.documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ paths:
features:
type: array
items:
$ref: "#/components/schemas/Schema1"
$ref: "#/components/schemas/Feature"
required:
- id
- name
Expand Down Expand Up @@ -867,15 +867,16 @@ paths:
message: Sample error message
components:
schemas:
Schema1:
Feature:
id: Feature
type: object
properties:
title:
type: string
features:
type: array
items:
$ref: "#/components/schemas/Schema1"
$ref: "#/components/schemas/Feature"
required:
- title
additionalProperties: false
Expand Down
7 changes: 4 additions & 3 deletions express-zod-api/src/documentation-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ import wellKnownHeaders from "./well-known-headers";
interface ReqResCommons {
makeRef: (
key: object | string,
subject: SchemaObject | ReferenceObject,
name?: string,
value: SchemaObject | ReferenceObject,
proposedName?: string,
) => ReferenceObject;
path: string;
method: ClientMethod;
Expand Down Expand Up @@ -323,7 +323,7 @@ export const depictRequestParams = ({
? makeRef(
jsonSchema.id || JSON.stringify(jsonSchema),
depicted,
makeCleanId(description, name),
jsonSchema.id || makeCleanId(description, name),
)
: depicted;
return acc.concat({
Expand Down Expand Up @@ -382,6 +382,7 @@ const fixReferences = (
entry.$ref = ctx.makeRef(
depiction.id || depiction, // avoiding serialization, because changing $ref
asOAS(depiction),
depiction.id,
Comment thread
RobinTail marked this conversation as resolved.
).$ref;
}
continue;
Expand Down
13 changes: 9 additions & 4 deletions express-zod-api/src/documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,19 @@ export class Documentation extends OpenApiBuilder {

#makeRef(
key: object | string,
subject: SchemaObject | ReferenceObject,
name = this.#references.get(key),
value: SchemaObject | ReferenceObject,
proposedName?: string,
): ReferenceObject {
let name = this.#references.get(key); // search in the cache by the given key
if (!name) {
name = `Schema${this.#references.size + 1}`;
let inc = proposedName ? 0 : 1;
do {
name = `${proposedName ?? "Schema"}${inc ? this.#references.size + inc : ""}`;
inc++;
} while (this.rootDoc.components?.schemas?.[name]); // search in existing references for the unique name
this.#references.set(key, name);
}
this.addSchema(name, subject);
this.addSchema(name, value);
return { $ref: `#/components/schemas/${name}` };
}

Expand Down
21 changes: 10 additions & 11 deletions express-zod-api/tests/__snapshots__/documentation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3834,7 +3834,7 @@ paths:
description: An optional cursor string used for pagination. This can be
retrieved from the \`next\` property of the previous page response.
schema:
$ref: "#/components/schemas/HeadHrisEmployeesParameterCursor"
$ref: "#/components/schemas/GetHrisEmployeesParameterCursor"
responses:
"200":
description: HEAD /hris/employees Positive response
Expand Down Expand Up @@ -3878,10 +3878,6 @@ components:
- status
- error
additionalProperties: false
HeadHrisEmployeesParameterCursor:
description: An optional cursor string used for pagination. This can be
retrieved from the \`next\` property of the previous page response.
type: string
responses: {}
parameters: {}
examples: {}
Expand Down Expand Up @@ -4970,11 +4966,7 @@ paths:
required: true
description: POST /v1/:name Parameter
schema:
anyOf:
- type: string
const: John
- type: string
const: Jane
$ref: "#/components/schemas/NameParam"
requestBody:
description: POST /v1/:name Request body
content:
Expand Down Expand Up @@ -5035,7 +5027,14 @@ paths:
error:
message: Sample error message
components:
schemas: {}
schemas:
NameParam:
id: NameParam
anyOf:
- type: string
const: John
- type: string
const: Jane
responses: {}
parameters: {}
examples: {}
Expand Down
5 changes: 4 additions & 1 deletion express-zod-api/tests/documentation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,10 @@ describe("Documentation", () => {
":name": defaultEndpointsFactory.build({
method: "post",
input: z.object({
name: z.literal("John").or(z.literal("Jane")),
name: z
.literal("John")
.or(z.literal("Jane"))
.meta({ id: "NameParam" }),
other: z.boolean(),
}),
output: z.object({}),
Expand Down