Skip to content

Commit 54b806d

Browse files
authored
Removing body depiction from negative response to HEAD requests (#2859)
Fixes #2858 Noticed by @coderabbitai in #2857 (comment) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated API documentation for several HEAD endpoints to remove detailed error response content, reflecting that no payload is returned for negative responses. * **Refactor** * Simplified logic for determining response content in HEAD requests, now relying solely on the HTTP method. * Streamlined type definitions for negative responses of HEAD endpoints to indicate no content is returned. * **Tests** * Updated tests to align with the simplified response content logic for HEAD requests. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent a92c610 commit 54b806d

8 files changed

Lines changed: 32 additions & 607 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Version 24
44

5+
### v24.7.3
6+
7+
- Fixed the depiction of the negative response to `HEAD` requests:
8+
- Should have no response body, exactly as the positive one;
9+
- This version corrects the implementation introduced in [v24.7.0](#v2470).
10+
511
### v24.7.2
612

713
- Fixed the negative response MIME type for ~~`arrayResultHandler`~~ (deprecated entity):
@@ -24,10 +30,10 @@
2430
- It is the built-in feature of Express to handle `HEAD` requests by the handlers for `GET` requests;
2531
- Therefore, each `Endpoint` supporting `get` method also handles `head` requests (no work needed);
2632
- Added `HEAD` method to CORS response headers, along with `OPTIONS`, for `GET` method supporting endpoints;
27-
- Positive response to `HEAD` request should contain same headers as `GET` would, but without the body:
33+
- ~~Positive~~ Response to `HEAD` request should contain same headers as `GET` would, but without the body:
2834
- Added `head` request depiction to the generated `Documentation`;
2935
- Added `head` request types to the generated `Integration` client;
30-
- Positive response to `HEAD` request should contain the `Content-Length` header:
36+
- ~~Positive~~ Response to `HEAD` request should contain the `Content-Length` header:
3137
- `ResultHandler`s using `response.send()` (as well as its shorthands such as `.json()`) automatically do that
3238
instead of sending the response body (no work needed);
3339
- Other approaches, such as stream piping, might require to implement `Content-Length` header for `HEAD` requests;

example/example.client.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,7 @@ interface HeadV1UserRetrievePositiveResponseVariants {
5454
}
5555

5656
/** head /v1/user/retrieve */
57-
type HeadV1UserRetrieveNegativeVariant1 = {
58-
status: "error";
59-
error: {
60-
message: string;
61-
};
62-
};
57+
type HeadV1UserRetrieveNegativeVariant1 = undefined;
6358

6459
/** head /v1/user/retrieve */
6560
interface HeadV1UserRetrieveNegativeResponseVariants {
@@ -198,7 +193,7 @@ interface HeadV1UserListPositiveResponseVariants {
198193
}
199194

200195
/** head /v1/user/list */
201-
type HeadV1UserListNegativeVariant1 = string;
196+
type HeadV1UserListNegativeVariant1 = undefined;
202197

203198
/** head /v1/user/list */
204199
interface HeadV1UserListNegativeResponseVariants {
@@ -240,7 +235,7 @@ interface HeadV1AvatarSendPositiveResponseVariants {
240235
}
241236

242237
/** head /v1/avatar/send */
243-
type HeadV1AvatarSendNegativeVariant1 = string;
238+
type HeadV1AvatarSendNegativeVariant1 = undefined;
244239

245240
/** head /v1/avatar/send */
246241
interface HeadV1AvatarSendNegativeResponseVariants {
@@ -282,7 +277,7 @@ interface HeadV1AvatarStreamPositiveResponseVariants {
282277
}
283278

284279
/** head /v1/avatar/stream */
285-
type HeadV1AvatarStreamNegativeVariant1 = string;
280+
type HeadV1AvatarStreamNegativeVariant1 = undefined;
286281

287282
/** head /v1/avatar/stream */
288283
interface HeadV1AvatarStreamNegativeResponseVariants {
@@ -395,7 +390,7 @@ interface HeadV1EventsStreamPositiveResponseVariants {
395390
}
396391

397392
/** head /v1/events/stream */
398-
type HeadV1EventsStreamNegativeVariant1 = string;
393+
type HeadV1EventsStreamNegativeVariant1 = undefined;
399394

400395
/** head /v1/events/stream */
401396
interface HeadV1EventsStreamNegativeResponseVariants {

example/example.documentation.yaml

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -100,32 +100,6 @@ paths:
100100
description: HEAD /v1/user/retrieve Positive response
101101
"400":
102102
description: HEAD /v1/user/retrieve Negative response
103-
content:
104-
application/json:
105-
schema:
106-
type: object
107-
properties:
108-
status:
109-
type: string
110-
const: error
111-
error:
112-
type: object
113-
properties:
114-
message:
115-
type: string
116-
required:
117-
- message
118-
additionalProperties: false
119-
required:
120-
- status
121-
- error
122-
additionalProperties: false
123-
examples:
124-
example1:
125-
value:
126-
status: error
127-
error:
128-
message: Sample error message
129103
/v1/user/{id}/remove:
130104
delete:
131105
operationId: DeleteV1UserIdRemove
@@ -456,13 +430,6 @@ paths:
456430
description: HEAD /v1/user/list Positive response
457431
"400":
458432
description: HEAD /v1/user/list Negative response
459-
content:
460-
text/plain:
461-
schema:
462-
type: string
463-
examples:
464-
example1:
465-
value: Sample error message
466433
/v1/avatar/send:
467434
get:
468435
operationId: GetV1AvatarSend
@@ -512,10 +479,6 @@ paths:
512479
description: HEAD /v1/avatar/send Positive response
513480
"400":
514481
description: HEAD /v1/avatar/send Negative response
515-
content:
516-
text/plain:
517-
schema:
518-
type: string
519482
/v1/avatar/stream:
520483
get:
521484
operationId: GetV1AvatarStream
@@ -565,10 +528,6 @@ paths:
565528
description: HEAD /v1/avatar/stream Positive response
566529
"400":
567530
description: HEAD /v1/avatar/stream Negative response
568-
content:
569-
text/plain:
570-
schema:
571-
type: string
572531
/v1/avatar/upload:
573532
post:
574533
operationId: PostV1AvatarUpload
@@ -790,10 +749,6 @@ paths:
790749
description: HEAD /v1/events/stream Positive response
791750
"400":
792751
description: HEAD /v1/events/stream Negative response
793-
content:
794-
text/plain:
795-
schema:
796-
type: string
797752
/v1/forms/feedback:
798753
post:
799754
operationId: PostV1FormsFeedback

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
Method,
1212
CORSMethod,
1313
} from "./method";
14-
import { ResponseVariant } from "./api-response";
14+
import { NormalizedResponse } from "./api-response";
1515

1616
/** @desc this type does not allow props assignment, but it works for reading them when merged with another interface */
1717
export type EmptyObject = z.output<EmptySchema>;
@@ -143,7 +143,8 @@ export const isProduction = R.memoizeWith(
143143
() => process.env.NODE_ENV === "production", // eslint-disable-line no-restricted-syntax -- memoized
144144
);
145145

146-
export const doesImplyContent = (
146+
export const shouldHaveContent = (
147147
method: ClientMethod,
148-
responseVariant: ResponseVariant,
149-
) => !(method === "head" && responseVariant === "positive");
148+
mimeTypes: NormalizedResponse["mimeTypes"],
149+
): mimeTypes is NonNullable<NormalizedResponse["mimeTypes"]> =>
150+
Boolean(mimeTypes) && method !== "head";

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ import {
2525
} from "openapi3-ts/oas31";
2626
import * as R from "ramda";
2727
import { z } from "zod/v4";
28-
import { ResponseVariant } from "./api-response";
28+
import { NormalizedResponse, ResponseVariant } from "./api-response";
2929
import { ezBufferBrand } from "./buffer-schema";
3030
import {
31-
doesImplyContent,
31+
shouldHaveContent,
3232
FlatObject,
3333
getRoutePathParams,
3434
getTransformedType,
@@ -494,12 +494,12 @@ export const depictResponse = ({
494494
composition: "inline" | "components";
495495
description?: string;
496496
brandHandling?: BrandHandling;
497-
mimeTypes: ReadonlyArray<string> | null;
497+
mimeTypes: NormalizedResponse["mimeTypes"];
498498
variant: ResponseVariant;
499499
statusCode: number;
500500
hasMultipleStatusCodes: boolean;
501501
}): ResponseObject => {
502-
if (!mimeTypes || !doesImplyContent(method, variant)) return { description };
502+
if (!shouldHaveContent(method, mimeTypes)) return { description };
503503
const response = asOAS(
504504
depict(schema, {
505505
rules: { ...brandHandling, ...depicters },

express-zod-api/src/integration.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
makeLiteralType,
1515
makeUnion,
1616
} from "./typescript-api";
17-
import { doesImplyContent, makeCleanId } from "./common-helpers";
17+
import { shouldHaveContent, makeCleanId } from "./common-helpers";
1818
import { loadPeer } from "./peer-helpers";
1919
import { Routing } from "./routing";
2020
import { OnEndpoint, walkRouting, withHead } from "./routing-walker";
@@ -107,8 +107,7 @@ export class Integration extends IntegrationBase {
107107
(agg, responseVariant) => {
108108
const responses = endpoint.getResponses(responseVariant);
109109
const props = R.chain(([idx, { schema, mimeTypes, statusCodes }]) => {
110-
const hasContent =
111-
mimeTypes && doesImplyContent(method, responseVariant);
110+
const hasContent = shouldHaveContent(method, mimeTypes);
112111
const variantType = makeType(
113112
entitle(responseVariant, "variant", `${idx + 1}`),
114113
zodToTs(hasContent ? schema : noContent, ctxOut),

0 commit comments

Comments
 (0)