Skip to content

Commit 2923a0f

Browse files
committed
fix: responseHeaders generation by inlining the type instead of referencing it
1 parent db7abfd commit 2923a0f

File tree

6 files changed

+104
-29
lines changed

6 files changed

+104
-29
lines changed

.changeset/good-showers-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"typed-openapi": patch
3+
---
4+
5+
Fix responseHeaders generation by inlining the type instead of referencing it.

packages/typed-openapi/src/generator.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,10 @@ const parameterObjectToString = (parameters: Box<AnyBoxDef> | Record<string, Any
170170
return str + "}";
171171
};
172172

173-
const responseHeadersObjectToString = (responseHeaders: Record<string, Box<BoxObject>>, ctx: GeneratorContext) => {
173+
const responseHeadersObjectToString = (responseHeaders: Record<string, Box<BoxObject>>) => {
174174
let str = "{";
175175
for (const [key, responseHeader] of Object.entries(responseHeaders)) {
176-
const value =
177-
ctx.runtime === "none"
178-
? responseHeader.recompute((box) => {
179-
if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
180-
box.value = `Schemas.${box.value}`;
181-
}
182-
183-
return box;
184-
}).value
185-
: responseHeader.value;
186-
str += `${wrapWithQuotesIfNeeded(key.toLowerCase())}: ${value},\n`;
176+
str += `${wrapWithQuotesIfNeeded(key.toLowerCase())}: ${responseHeader.value},\n`;
187177
}
188178
return str + "}";
189179
};
@@ -245,7 +235,7 @@ const generateEndpointSchemaList = (ctx: GeneratorContext) => {
245235
${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""}
246236
${
247237
endpoint.responseHeaders
248-
? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},`
238+
? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders)},`
249239
: ""
250240
}
251241
}\n`;

packages/typed-openapi/tests/generator.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ describe("generator", () => {
186186
query: Partial<{ username: string; password: string }>;
187187
};
188188
responses: { 200: string; 400: unknown };
189-
responseHeaders: { 200: { "X-Rate-Limit": unknown; "X-Expires-After": unknown }; 400: { "X-Error": unknown } };
189+
responseHeaders: { 200: { "X-Rate-Limit": number; "X-Expires-After": string }; 400: { "X-Error": string } };
190190
};
191191
export type get_LogoutUser = {
192192
method: "GET";
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { describe, test } from "vitest";
2+
import { mapOpenApiEndpoints } from "../src/map-openapi-endpoints.ts";
3+
import type { OpenAPIObject } from "openapi3-ts/oas31";
4+
import { generateFile } from "../src/generator.ts";
5+
import { prettify } from "../src/pretty.export.ts";
6+
7+
const openApiDoc: OpenAPIObject = {
8+
openapi: "3.0.3",
9+
info: { title: "Test API Spec", version: "1.0.0" },
10+
paths: {
11+
"/test": {
12+
get: {
13+
operationId: "getTest",
14+
responses: {
15+
"200": {
16+
description: "Response",
17+
content: {
18+
"application/json": {
19+
schema: {
20+
type: "string"
21+
},
22+
},
23+
},
24+
headers: {
25+
"X-RateLimit-Limit": {
26+
$ref: "#/components/headers/x-rate-limit-limit",
27+
},
28+
"X-RateLimit-Remaining": {
29+
$ref: "#/components/headers/x-rate-limit-remaining",
30+
},
31+
"X-RateLimit-Reset": {
32+
$ref: "#/components/headers/x-rate-limit-reset",
33+
},
34+
},
35+
},
36+
},
37+
},
38+
},
39+
},
40+
components: {
41+
headers: {
42+
"x-rate-limit-limit": {
43+
example: 5000,
44+
schema: {
45+
type: "integer",
46+
},
47+
},
48+
"x-rate-limit-remaining": {
49+
example: 4999,
50+
schema: {
51+
type: "integer",
52+
},
53+
},
54+
"x-rate-limit-reset": {
55+
example: 1590701888,
56+
schema: {
57+
type: "integer",
58+
format: "timestamp",
59+
},
60+
},
61+
},
62+
},
63+
};
64+
65+
describe("namespaced header ref is wrong", () => {
66+
test("should not resolve response as unknown", async ({ expect }) => {
67+
const result = mapOpenApiEndpoints(openApiDoc);
68+
// Find the endpoint by alias (see getAlias logic: 'get_GetTest')
69+
const endpoint = result.endpointList.find((e) => e.meta.alias === "get_GetTest");
70+
expect(endpoint).toBeDefined();
71+
72+
const response200 = endpoint?.responseHeaders?.["200"];
73+
expect(response200?.value).toBe('{ "X-RateLimit-Limit": number, "X-RateLimit-Remaining": number, "X-RateLimit-Reset": number }')
74+
75+
76+
const file = generateFile(result);
77+
const output = await prettify(file);
78+
expect(output).toContain(`{ "X-RateLimit-Limit": number; "X-RateLimit-Remaining": number; "X-RateLimit-Reset": number }`);
79+
});
80+
});

packages/typed-openapi/tests/snapshots/docker.openapi.client.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ export namespace Endpoints {
11891189
path: { id: string };
11901190
};
11911191
responses: { 200: unknown; 400: Schemas.ErrorResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse };
1192-
responseHeaders: { 200: { "X-Docker-Container-Path-Stat": unknown } };
1192+
responseHeaders: { 200: { "X-Docker-Container-Path-Stat": string } };
11931193
};
11941194
export type post_ContainerPrune = {
11951195
method: "POST";
@@ -1411,14 +1411,14 @@ export namespace Endpoints {
14111411
responses: { 200: unknown; 500: unknown };
14121412
responseHeaders: {
14131413
200: {
1414-
Swarm: unknown;
1415-
"Docker-Experimental": unknown;
1416-
"Cache-Control": unknown;
1417-
Pragma: unknown;
1418-
"API-Version": unknown;
1419-
"Builder-Version": unknown;
1414+
Swarm: "inactive" | "pending" | "error" | "locked" | "active/worker" | "active/manager";
1415+
"Docker-Experimental": boolean;
1416+
"Cache-Control": string;
1417+
Pragma: string;
1418+
"API-Version": string;
1419+
"Builder-Version": string;
14201420
};
1421-
500: { "Cache-Control": unknown; Pragma: unknown };
1421+
500: { "Cache-Control": string; Pragma: string };
14221422
};
14231423
};
14241424
export type head_SystemPingHead = {
@@ -1429,12 +1429,12 @@ export namespace Endpoints {
14291429
responses: { 200: unknown; 500: unknown };
14301430
responseHeaders: {
14311431
200: {
1432-
Swarm: unknown;
1433-
"Docker-Experimental": unknown;
1434-
"Cache-Control": unknown;
1435-
Pragma: unknown;
1436-
"API-Version": unknown;
1437-
"Builder-Version": unknown;
1432+
Swarm: "inactive" | "pending" | "error" | "locked" | "active/worker" | "active/manager";
1433+
"Docker-Experimental": boolean;
1434+
"Cache-Control": string;
1435+
Pragma: string;
1436+
"API-Version": string;
1437+
"Builder-Version": string;
14381438
};
14391439
};
14401440
};

packages/typed-openapi/tests/snapshots/petstore.client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export namespace Endpoints {
175175
query: Partial<{ username: string; password: string }>;
176176
};
177177
responses: { 200: string; 400: unknown };
178-
responseHeaders: { 200: { "X-Rate-Limit": unknown; "X-Expires-After": unknown }; 400: { "X-Error": unknown } };
178+
responseHeaders: { 200: { "X-Rate-Limit": number; "X-Expires-After": string }; 400: { "X-Error": string } };
179179
};
180180
export type get_LogoutUser = {
181181
method: "GET";

0 commit comments

Comments
 (0)