fork of hey-api/openapi-ts because I need some additional things
1import type { Context } from './context';
2import type { Pagination } from './pagination';
3import { hasParametersObjectRequired, parameterWithPagination } from './parameter';
4import { deduplicateSchema } from './schema';
5import type { IR } from './types';
6import { addItemsToSchema } from './utils';
7
8export const hasOperationDataRequired = (operation: IR.OperationObject): boolean => {
9 if (hasParametersObjectRequired(operation.parameters)) {
10 return true;
11 }
12
13 if (operation.body?.required) {
14 return true;
15 }
16
17 return false;
18};
19
20export const createOperationKey = ({ method, path }: { method: string; path: string }) =>
21 `${method.toUpperCase()} ${path}`;
22
23export const operationPagination = ({
24 context,
25 operation,
26}: {
27 context: Context;
28 operation: IR.OperationObject;
29}): Pagination | undefined => {
30 const body = operation.body;
31
32 if (!body || !body.pagination) {
33 return parameterWithPagination({
34 context,
35 parameters: operation.parameters,
36 });
37 }
38
39 if (body.pagination === true) {
40 return {
41 in: 'body',
42 name: 'body',
43 schema: body.schema,
44 };
45 }
46
47 const schema = body.schema;
48 const resolvedSchema = schema.$ref
49 ? context.resolveIrRef<IR.RequestBodyObject | IR.SchemaObject>(schema.$ref)
50 : schema;
51
52 const finalSchema = 'schema' in resolvedSchema ? resolvedSchema.schema : resolvedSchema;
53 const paginationProp = finalSchema?.properties?.[body.pagination];
54
55 if (!paginationProp) {
56 return parameterWithPagination({
57 context,
58 parameters: operation.parameters,
59 });
60 }
61
62 return {
63 in: 'body',
64 name: body.pagination,
65 schema: paginationProp,
66 };
67};
68
69type StatusGroup = '1XX' | '2XX' | '3XX' | '4XX' | '5XX' | 'default';
70
71export const statusCodeToGroup = ({ statusCode }: { statusCode: string }): StatusGroup => {
72 switch (statusCode) {
73 case '1XX':
74 return '1XX';
75 case '2XX':
76 return '2XX';
77 case '3XX':
78 return '3XX';
79 case '4XX':
80 return '4XX';
81 case '5XX':
82 return '5XX';
83 case 'default':
84 return 'default';
85 default:
86 return `${statusCode[0]}XX` as StatusGroup;
87 }
88};
89
90interface OperationResponsesMap {
91 /**
92 * A deduplicated union of all error types. Unknown types are omitted.
93 */
94 error?: IR.SchemaObject;
95 /**
96 * An object containing a map of status codes for each error type.
97 */
98 errors?: IR.SchemaObject;
99 /**
100 * A deduplicated union of all response types. Unknown types are omitted.
101 */
102 response?: IR.SchemaObject;
103 /**
104 * An object containing a map of status codes for each response type.
105 */
106 responses?: IR.SchemaObject;
107}
108
109export const operationResponsesMap = (operation: IR.OperationObject): OperationResponsesMap => {
110 const result: OperationResponsesMap = {};
111
112 if (!operation.responses) {
113 return result;
114 }
115
116 const errors: Omit<IR.SchemaObject, 'properties'> &
117 Pick<Required<IR.SchemaObject>, 'properties'> = {
118 properties: {},
119 type: 'object',
120 };
121
122 const responses: Omit<IR.SchemaObject, 'properties'> &
123 Pick<Required<IR.SchemaObject>, 'properties'> = {
124 properties: {},
125 type: 'object',
126 };
127
128 // store default response to be evaluated last
129 let defaultResponse: IR.ResponseObject | undefined;
130
131 for (const name in operation.responses) {
132 const response = operation.responses[name]!;
133
134 switch (statusCodeToGroup({ statusCode: name })) {
135 case '1XX':
136 case '3XX':
137 // TODO: parser - handle informational and redirection status codes
138 break;
139 case '2XX':
140 responses.properties[name] = response.schema;
141 break;
142 case '4XX':
143 case '5XX':
144 errors.properties[name] = response.schema;
145 break;
146 case 'default':
147 defaultResponse = response;
148 break;
149 }
150 }
151
152 // infer default response type
153 if (defaultResponse) {
154 let inferred = false;
155
156 // assume default is intended for success if none exists yet
157 if (!Object.keys(responses.properties).length) {
158 responses.properties.default = defaultResponse.schema;
159 inferred = true;
160 }
161
162 const description = (defaultResponse.schema.description ?? '').toLocaleLowerCase();
163 const $ref = (defaultResponse.schema.$ref ?? '').toLocaleLowerCase();
164
165 // TODO: parser - this could be rewritten using regular expressions
166 const successKeywords = ['success'];
167 if (
168 successKeywords.some((keyword) => description.includes(keyword) || $ref.includes(keyword))
169 ) {
170 responses.properties.default = defaultResponse.schema;
171 inferred = true;
172 }
173
174 // TODO: parser - this could be rewritten using regular expressions
175 const errorKeywords = ['error', 'problem'];
176 if (errorKeywords.some((keyword) => description.includes(keyword) || $ref.includes(keyword))) {
177 errors.properties.default = defaultResponse.schema;
178 inferred = true;
179 }
180
181 // if no keyword match, assume default schema is intended for error
182 if (!inferred) {
183 errors.properties.default = defaultResponse.schema;
184 }
185 }
186
187 const errorKeys = Object.keys(errors.properties);
188 if (errorKeys.length) {
189 errors.required = errorKeys;
190 result.errors = errors;
191
192 let errorUnion = addItemsToSchema({
193 items: Object.values(errors.properties),
194 mutateSchemaOneItem: true,
195 schema: {},
196 });
197 errorUnion = deduplicateSchema({ schema: errorUnion });
198 if (Object.keys(errorUnion).length && errorUnion.type !== 'unknown') {
199 result.error = errorUnion;
200 }
201 }
202
203 const responseKeys = Object.keys(responses.properties);
204 if (responseKeys.length) {
205 responses.required = responseKeys;
206 result.responses = responses;
207
208 let responseUnion = addItemsToSchema({
209 items: Object.values(responses.properties),
210 mutateSchemaOneItem: true,
211 schema: {},
212 });
213 responseUnion = deduplicateSchema({ schema: responseUnion });
214 if (Object.keys(responseUnion).length && responseUnion.type !== 'unknown') {
215 result.response = responseUnion;
216 }
217 }
218
219 return result;
220};