fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 220 lines 6.0 kB view raw
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};