Skip to content

Commit 91ad0a8

Browse files
committed
support component response headers
1 parent e44ea1a commit 91ad0a8

File tree

7 files changed

+125
-57
lines changed

7 files changed

+125
-57
lines changed

src/oas3/codegen/endpointUtils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,43 @@ import {CodeGenerator} from './core';
22
import {
33
ConcreteParameterLocation,
44
findConcreteParameter,
5+
findConcreteResponseHeader,
56
ObjectSchema,
67
ObjectSchemaProps,
78
Parameter,
9+
ResponseHeaderByNameMap,
810
} from '@/oas3/specification';
911

12+
// todo: rename to createResponseHeadersObjectSchema
13+
export function createHeadersObjectSchema(
14+
codeGenerator: CodeGenerator,
15+
responseHeaderByName: ResponseHeaderByNameMap
16+
): ObjectSchema {
17+
const requiredProps: string[] = [];
18+
const props: ObjectSchemaProps = {};
19+
for (const headerName in responseHeaderByName) {
20+
const responseHeader = responseHeaderByName[headerName];
21+
const concreteResponseHeader = findConcreteResponseHeader(
22+
codeGenerator.getSpecification(),
23+
responseHeader
24+
);
25+
if (!concreteResponseHeader) {
26+
throw new Error(
27+
`could not find concrete response header from: ${JSON.stringify(responseHeader)}`
28+
);
29+
}
30+
if (concreteResponseHeader.required) {
31+
requiredProps.push(headerName);
32+
}
33+
props[headerName] = concreteResponseHeader.schema;
34+
}
35+
return {
36+
type: 'object',
37+
properties: props,
38+
required: requiredProps,
39+
};
40+
}
41+
1042
export function findObjectSchemaFromLocationParameters(
1143
codeGenerator: CodeGenerator,
1244
requestParameters: Parameter[],

src/oas3/codegen/response.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import {
2-
ObjectSchema,
3-
ObjectSchemaProps,
42
ConcreteResponse,
53
ResponseBodyContent,
64
ResponseHeaderByNameMap,
7-
isStringSchema,
85
Response,
96
isResponseComponentRef,
107
isConcreteResponse,
11-
findConcreteSchema,
128
ResponseComponentRef,
139
} from '@/oas3/specification';
1410
import {
@@ -27,34 +23,7 @@ import {
2723
templateResponseUnionType,
2824
} from './template';
2925
import {applyNullableFormDataTypeDefinition} from './formData';
30-
31-
function createHeadersObjectSchema(
32-
codeGenerator: CodeGenerator,
33-
headersSchema: ResponseHeaderByNameMap
34-
): ObjectSchema {
35-
const requiredProps: string[] = [];
36-
const props: ObjectSchemaProps = {};
37-
for (const headerName in headersSchema) {
38-
requiredProps.push(headerName);
39-
const headerSchema = headersSchema[headerName].schema;
40-
const concreteHeaderSchema = findConcreteSchema(
41-
codeGenerator.getSpecification(),
42-
headerSchema
43-
);
44-
if (isStringSchema(concreteHeaderSchema)) {
45-
props[headerName] = headerSchema;
46-
continue;
47-
}
48-
props[headerName] = {
49-
type: 'string',
50-
};
51-
}
52-
return {
53-
type: 'object',
54-
properties: props,
55-
required: requiredProps,
56-
};
57-
}
26+
import {createHeadersObjectSchema} from './endpointUtils';
5827

5928
function applyResponseHeaders(
6029
codeGenerator: CodeGenerator,

src/oas3/codegen/responseSchema.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ import {
22
ConcreteResponse,
33
isConcreteResponse,
44
isResponseComponentRef,
5-
ObjectSchema,
6-
ObjectSchemaProps,
75
RequestBodyContent,
86
Response,
97
ResponseBodyContentByContentTypeMap,
108
ResponseComponentRef,
11-
ResponseHeaderByNameMap,
129
} from '@/oas3/specification';
1310
import {
1411
CodeGenerationOutput,
@@ -20,6 +17,7 @@ import {
2017
} from './core';
2118
import {Context} from './generator';
2219
import {applyZodSchema} from './zodSchema';
20+
import {createHeadersObjectSchema} from './endpointUtils';
2321

2422
type ApplyResponseBodyResult = {
2523
contentType: string;
@@ -118,22 +116,6 @@ function applyResponseBodyByContentTypeMap(
118116
};
119117
}
120118

121-
function createHeadersObjectSchema(
122-
headersSchema: ResponseHeaderByNameMap
123-
): ObjectSchema {
124-
const requiredProps: string[] = [];
125-
const props: ObjectSchemaProps = {};
126-
for (const headerName in headersSchema) {
127-
requiredProps.push(headerName);
128-
props[headerName] = headersSchema[headerName].schema;
129-
}
130-
return {
131-
type: 'object',
132-
properties: props,
133-
required: requiredProps,
134-
};
135-
}
136-
137119
function applyConcreteResponseSchema(
138120
codeGenerator: CodeGenerator,
139121
schema: ConcreteResponse,
@@ -142,7 +124,10 @@ function applyConcreteResponseSchema(
142124
): CodeGenerationOutput {
143125
let headersZodSchemaCodeOutput: undefined | CodeGenerationOutput;
144126
if (ctx.config.withZod && schema.headers) {
145-
const headersObjectSchema = createHeadersObjectSchema(schema.headers);
127+
const headersObjectSchema = createHeadersObjectSchema(
128+
codeGenerator,
129+
schema.headers
130+
);
146131
headersZodSchemaCodeOutput = applyZodSchema(
147132
codeGenerator,
148133
headersObjectSchema,

src/oas3/specification/componentRef.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,19 @@ export function isSecuritySchemesComponentRef(
5555
): anyValue is SecurityComponentRef {
5656
return zSecuritySchemeComponentRef.safeParse(anyValue).success;
5757
}
58+
59+
export const responseHeaderComponentRefPrefix = '#/components/headers/';
60+
61+
export const zResponseHeaderComponentRef = z.object({
62+
$ref: z.string().startsWith(responseHeaderComponentRefPrefix),
63+
});
64+
65+
export type ResponseHeaderComponentRef = z.infer<
66+
typeof zResponseHeaderComponentRef
67+
>;
68+
69+
export function isResponseHeaderComponentRef(
70+
anyValue: unknown
71+
): anyValue is ResponseHeaderComponentRef {
72+
return zResponseHeaderComponentRef.safeParse(anyValue).success;
73+
}

src/oas3/specification/response.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import {zSchema} from './schema';
2-
import {zResponseComponentRef} from './componentRef';
2+
import {
3+
zResponseComponentRef,
4+
zResponseHeaderComponentRef,
5+
} from './componentRef';
36
import {z} from 'zod';
47

58
export const zResponseBodyContent = z.object({
@@ -15,12 +18,29 @@ export type ResponseBodyContentByContentTypeMap = z.infer<
1518
typeof zResponseBodyContentByContentTypeMap
1619
>;
1720

18-
export const zResponseHeader = z.object({
21+
export const zConcreteResponseHeader = z.object({
1922
schema: zSchema,
23+
required: z.boolean().optional(),
24+
description: z.string().optional(),
2025
});
2126

27+
export function isConcreteResponseHeader(
28+
anyValue: unknown
29+
): anyValue is ConcreteResponseHeader {
30+
return zConcreteResponseHeader.safeParse(anyValue).success;
31+
}
32+
33+
export type ConcreteResponseHeader = z.infer<typeof zConcreteResponseHeader>;
34+
35+
export const zResponseHeader = z.union([
36+
zConcreteResponseHeader,
37+
zResponseHeaderComponentRef,
38+
]);
39+
2240
export const zResponseHeaderByNameMap = z.record(zResponseHeader);
2341

42+
export type ResponseHeader = z.infer<typeof zResponseHeader>;
43+
2444
export type ResponseHeaderByNameMap = z.infer<typeof zResponseHeaderByNameMap>;
2545

2646
export const zConcreteResponse = z.object({

src/oas3/specification/specification.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {zConcreteResponse} from './response';
1+
import {zResponse, zResponseHeaderByNameMap} from './response';
22
import {zSchema} from './schema';
33
import {zSecurityScheme} from './security';
44
import {zEndpoint} from './endpoint';
@@ -10,7 +10,7 @@ export type RequestByMethodMap = z.infer<typeof zRequestByMethodMap>;
1010

1111
const zRequestDefinitionsByPathMap = z.record(zRequestByMethodMap);
1212

13-
const zResponseByNameMap = z.record(zConcreteResponse);
13+
const zResponseByNameMap = z.record(zResponse);
1414

1515
const zSchemaByNameMap = z.record(zSchema);
1616

@@ -21,6 +21,7 @@ const zRequestParameterByNameMap = z.record(zParameter);
2121
const zComponentDefinitions = z.object({
2222
parameters: zRequestParameterByNameMap.optional(),
2323
responses: zResponseByNameMap.optional(),
24+
headers: zResponseHeaderByNameMap.optional(),
2425
schemas: zSchemaByNameMap.optional(),
2526
securitySchemes: zSecuritySchemeByNameMap.optional(),
2627
});

src/oas3/specification/util.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import {
22
isParameterComponentRef,
33
isResponseComponentRef,
4+
isResponseHeaderComponentRef,
45
isSchemaComponentRef,
56
parameterComponentRefPrefix,
67
responseComponentRefPrefix,
8+
responseHeaderComponentRefPrefix,
79
schemaComponentRefPrefix,
810
} from './componentRef';
911
import {ConcreteSchema, isConcreteSchema, Schema} from './schema';
1012
import {ConcreteParameter, isConcreteParameter, Parameter} from './endpoint';
11-
import {ConcreteResponse, isConcreteResponse, Response} from './response';
13+
import {
14+
ConcreteResponse,
15+
ConcreteResponseHeader,
16+
isConcreteResponse,
17+
isConcreteResponseHeader,
18+
Response,
19+
ResponseHeader,
20+
} from './response';
1221
import {Specification} from './specification';
1322

1423
export function findComponentSchemaByRef(
@@ -106,3 +115,39 @@ export function findConcreteResponse(
106115
}
107116
return findConcreteResponse(spec, componentResponse);
108117
}
118+
119+
export function findComponentResponseHeaderByRef(
120+
spec: Specification,
121+
componentRef: string
122+
): null | ResponseHeader {
123+
const components = spec.components;
124+
if (
125+
componentRef.startsWith(responseHeaderComponentRefPrefix) &&
126+
components.headers
127+
) {
128+
const name = componentRef.replace(responseHeaderComponentRefPrefix, '');
129+
const responseHeader = components.headers[name];
130+
return responseHeader ?? null;
131+
}
132+
return null;
133+
}
134+
135+
export function findConcreteResponseHeader(
136+
spec: Specification,
137+
responseHeader: ResponseHeader
138+
): null | ConcreteResponseHeader {
139+
if (isConcreteResponseHeader(responseHeader)) {
140+
return responseHeader;
141+
}
142+
if (!isResponseHeaderComponentRef(responseHeader)) {
143+
return null;
144+
}
145+
const componentResponseHeader = findComponentResponseHeaderByRef(
146+
spec,
147+
responseHeader.$ref
148+
);
149+
if (!componentResponseHeader) {
150+
return null;
151+
}
152+
return findConcreteResponseHeader(spec, componentResponseHeader);
153+
}

0 commit comments

Comments
 (0)