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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Version 25

### v25.1.0

- Ability to disable the depiction of the `HEAD` method:
- New option `hasHeadMethod` on the argument of `Documentation` and `Integration` constructors;
- Depicts the HEAD method for each Endpoint supporting the GET method;
- The option is enabled by default (the behaviour introduced in [v24.7.0](#v2470));
- The feature suggested by [@GreaterTamarack](https://github.com/GreaterTamarack).

### v25.0.0

- Supported Node.js versions: `^20.19.0 || ^22.12.0 || ^24.0.0`;
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ 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/GreaterTamarack.png" alt="@GreaterTamarack" width="50px" />](https://github.com/GreaterTamarack)
[<img src="https://github.com/pepegc.png" alt="@pepegc" width="50px" />](https://github.com/pepegc)
[<img src="https://github.com/MichaelHindley.png" alt="@MichaelHindley" width="50px" />](https://github.com/MichaelHindley)
[<img src="https://github.com/zoton2.png" alt="@zoton2" width="50px" />](https://github.com/zoton2)
Expand Down
8 changes: 7 additions & 1 deletion express-zod-api/src/documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ interface DocumentationParams {
descriptions?: Partial<Record<Component, Descriptor>>;
/** @default true */
hasSummaryFromDescription?: boolean;
/**
* @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express)
* @default true
* */
hasHeadMethod?: boolean;
/** @default inline */
composition?: "inline" | "components";
/**
Expand Down Expand Up @@ -149,6 +154,7 @@ export class Documentation extends OpenApiBuilder {
tags,
isHeader,
hasSummaryFromDescription = true,
hasHeadMethod = true,
composition = "inline",
}: DocumentationParams) {
super();
Expand Down Expand Up @@ -256,7 +262,7 @@ export class Documentation extends OpenApiBuilder {
};
walkRouting({
routing,
onEndpoint: withHead(onEndpoint),
onEndpoint: hasHeadMethod ? withHead(onEndpoint) : onEndpoint,
});
if (tags) this.rootDoc.tags = depictTags(tags);
}
Expand Down
8 changes: 7 additions & 1 deletion express-zod-api/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ interface IntegrationParams {
* @default z.undefined()
* */
noContent?: z.ZodType;
/**
* @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express)
* @default true
* */
hasHeadMethod?: boolean;
/**
* @desc Handling rules for your own branded schemas.
* @desc Keys: brands (recommended to use unique symbols).
Expand Down Expand Up @@ -90,6 +95,7 @@ export class Integration extends IntegrationBase {
subscriptionClassName = "Subscription",
serverUrl = "https://example.com",
noContent = z.undefined(),
hasHeadMethod = true,
}: IntegrationParams) {
super(serverUrl);
const commons = { makeAlias: this.#makeAlias.bind(this) };
Expand Down Expand Up @@ -148,7 +154,7 @@ export class Integration extends IntegrationBase {
};
walkRouting({
routing,
onEndpoint: withHead(onEndpoint),
onEndpoint: hasHeadMethod ? withHead(onEndpoint) : onEndpoint,
});
this.#program.unshift(...this.#aliases.values());
this.#program.push(
Expand Down
34 changes: 0 additions & 34 deletions express-zod-api/tests/__snapshots__/documentation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -789,40 +789,6 @@ paths:
status: error
error:
message: Sample error message
head:
operationId: HeadV1GetSomething
parameters:
- name: array
in: query
required: true
description: HEAD /v1/getSomething Parameter
schema:
minItems: 1
maxItems: 3
type: array
items:
type: integer
exclusiveMinimum: 0
maximum: 9007199254740991
- name: unlimited
in: query
required: true
description: HEAD /v1/getSomething Parameter
schema:
type: array
items:
type: boolean
- name: transformer
in: query
required: true
description: HEAD /v1/getSomething Parameter
schema:
type: string
responses:
"200":
description: HEAD /v1/getSomething Positive response
"400":
description: HEAD /v1/getSomething Negative response
components:
schemas: {}
responses: {}
Expand Down
155 changes: 155 additions & 0 deletions express-zod-api/tests/__snapshots__/integration.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,161 @@ export type Request = keyof Input;
"
`;

exports[`Integration > Should support HEAD method by default 0 1`] = `
"type SomeOf<T> = T[keyof T];

/** get /v1/path */
type GetV1PathInput = {
some: string;
};

/** get /v1/path */
type GetV1PathPositiveVariant1 = {
status: "success";
data: {};
};

/** get /v1/path */
interface GetV1PathPositiveResponseVariants {
200: GetV1PathPositiveVariant1;
}

/** get /v1/path */
type GetV1PathNegativeVariant1 = {
status: "error";
error: {
message: string;
};
};

/** get /v1/path */
interface GetV1PathNegativeResponseVariants {
400: GetV1PathNegativeVariant1;
}

/** head /v1/path */
type HeadV1PathInput = {
some: string;
};

/** head /v1/path */
type HeadV1PathPositiveVariant1 = undefined;

/** head /v1/path */
interface HeadV1PathPositiveResponseVariants {
200: HeadV1PathPositiveVariant1;
}

/** head /v1/path */
type HeadV1PathNegativeVariant1 = undefined;

/** head /v1/path */
interface HeadV1PathNegativeResponseVariants {
400: HeadV1PathNegativeVariant1;
}

export type Path = "/v1/path";

export type Method = "get" | "post" | "put" | "delete" | "patch" | "head";

export interface Input {
"get /v1/path": GetV1PathInput;
"head /v1/path": HeadV1PathInput;
}

export interface PositiveResponse {
"get /v1/path": SomeOf<GetV1PathPositiveResponseVariants>;
"head /v1/path": SomeOf<HeadV1PathPositiveResponseVariants>;
}

export interface NegativeResponse {
"get /v1/path": SomeOf<GetV1PathNegativeResponseVariants>;
"head /v1/path": SomeOf<HeadV1PathNegativeResponseVariants>;
}

export interface EncodedResponse {
"get /v1/path": GetV1PathPositiveResponseVariants &
GetV1PathNegativeResponseVariants;
"head /v1/path": HeadV1PathPositiveResponseVariants &
HeadV1PathNegativeResponseVariants;
}

export interface Response {
"get /v1/path":
| PositiveResponse["get /v1/path"]
| NegativeResponse["get /v1/path"];
"head /v1/path":
| PositiveResponse["head /v1/path"]
| NegativeResponse["head /v1/path"];
}

export type Request = keyof Input;
"
`;

exports[`Integration > Should support HEAD method by default 1 1`] = `
"type SomeOf<T> = T[keyof T];

/** get /v1/path */
type GetV1PathInput = {
some: string;
};

/** get /v1/path */
type GetV1PathPositiveVariant1 = {
status: "success";
data: {};
};

/** get /v1/path */
interface GetV1PathPositiveResponseVariants {
200: GetV1PathPositiveVariant1;
}

/** get /v1/path */
type GetV1PathNegativeVariant1 = {
status: "error";
error: {
message: string;
};
};

/** get /v1/path */
interface GetV1PathNegativeResponseVariants {
400: GetV1PathNegativeVariant1;
}

export type Path = "/v1/path";

export type Method = "get" | "post" | "put" | "delete" | "patch" | "head";

export interface Input {
"get /v1/path": GetV1PathInput;
}

export interface PositiveResponse {
"get /v1/path": SomeOf<GetV1PathPositiveResponseVariants>;
}

export interface NegativeResponse {
"get /v1/path": SomeOf<GetV1PathNegativeResponseVariants>;
}

export interface EncodedResponse {
"get /v1/path": GetV1PathPositiveResponseVariants &
GetV1PathNegativeResponseVariants;
}

export interface Response {
"get /v1/path":
| PositiveResponse["get /v1/path"]
| NegativeResponse["get /v1/path"];
}

export type Request = keyof Input;
"
`;

exports[`Integration > Should support multiple response schemas depending on status code 1`] = `
"type SomeOf<T> = T[keyof T];

Expand Down
1 change: 1 addition & 0 deletions express-zod-api/tests/documentation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe("Documentation", () => {
const literalValue = "something" as const;
const spec = new Documentation({
config: sampleConfig,
hasHeadMethod: false,
routing: {
v1: {
getSomething: defaultEndpointsFactory.build({
Expand Down
19 changes: 19 additions & 0 deletions express-zod-api/tests/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ describe("Integration", () => {
expect(await client.printFormatted()).toMatchSnapshot();
});

test.each([undefined, false])(
"Should support HEAD method by default %#",
async (hasHeadMethod) => {
const client = new Integration({
hasHeadMethod,
variant: "types",
routing: {
v1: {
"get path": defaultEndpointsFactory.buildVoid({
input: z.object({ some: z.string() }),
handler: vi.fn(),
}),
},
},
});
expect(await client.printFormatted()).toMatchSnapshot();
},
);

test("Should support multiple response schemas depending on status code", async () => {
const factory = new EndpointsFactory(
new ResultHandler({
Expand Down