From be3995c56ba816725fe7de641a6c6fa492d45032 Mon Sep 17 00:00:00 2001 From: Aleksandr Yackovlev Date: Sat, 18 Jan 2020 22:30:30 +0300 Subject: [PATCH] feat(ts-template): handle multiple status codes in responses --- src/compile/ts/compileOperations.js | 68 +++++++++++++++++------------ src/templates/ts/request.handlebars | 51 +++++++++++++++++----- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/compile/ts/compileOperations.js b/src/compile/ts/compileOperations.js index 3b19265..fac57fd 100644 --- a/src/compile/ts/compileOperations.js +++ b/src/compile/ts/compileOperations.js @@ -1,8 +1,8 @@ -import { cloneDeep } from 'lodash'; +import { cloneDeep, toPairs } from 'lodash'; import { schema2typescript, capitalize } from '../../utils'; -import { parseParams, parseBody, prepareMimeType } from '../utils'; +import { parseParams, parseBody, parseResponses, prepareMimeType } from '../utils'; import { compile } from './compileModels'; @@ -13,7 +13,7 @@ const createOperations = (api, deref) => ...result, ...Object.keys(deref.paths[path]).map(async (method) => { const operation = deref.paths[path][method]; - const unrefedOperation = api.paths[path][method]; + // const unrefedOperation = api.paths[path][method]; let schema = { method: method.toUpperCase(), @@ -48,34 +48,44 @@ const createOperations = (api, deref) => let responseModel = `export type ${capitalize(schema.name)}Response = unknown;`; - // Support only 200 responses with the Content-Type application/json - if (operation.responses && (operation.responses['200'] || operation.responses['201'])) { - if ( - operation.responses['200'] && - operation.responses['200'].content && - operation.responses['200'].content['application/json'] - ) { - responseModel = await schema2typescript( - unrefedOperation.responses['200'].content['application/json'].schema, - `${schema.name}Response` - ); - - schema.response = operation.responses['200'].content['application/json'].schema; - } else if ( - operation.responses['201'] && - operation.responses['201'].content && - operation.responses['201'].content['application/json'] - ) { - responseModel = await schema2typescript( - unrefedOperation.responses['201'].content['application/json'].schema, - `${schema.name}Response` - ); - - schema.response = operation.responses['201'].content['application/json'].schema; + if (operation.responses) { + const resultResponses = parseResponses(operation.responses); + + let nextResponseModel = ''; + + const models = await Promise.all( + toPairs(resultResponses.responses).map(async ([statusCode, { response }]) => { + if (response) { + const modelWithStatusCode = await schema2typescript( + response, + `${schema.name}Response${statusCode}` + ); + + nextResponseModel = `${nextResponseModel}${ + nextResponseModel ? ' | ' : '' + }${capitalize(schema.name)}Response${statusCode}`; + + return modelWithStatusCode; + } + + return ''; + }) + ); + + if (nextResponseModel) { + responseModel = `export type ${capitalize( + schema.name + )}Response = ${nextResponseModel};`; } - } - schema.models = schema.models ? `${schema.models}\n${responseModel}` : responseModel; + schema = { + ...schema, + ...resultResponses, + models: schema.models + ? `${schema.models}\n${models.join('\n')}\n${responseModel}` + : `${models.join('\n')}\n${responseModel}`, + }; + } return schema; }), diff --git a/src/templates/ts/request.handlebars b/src/templates/ts/request.handlebars index 60cf919..76fc043 100644 --- a/src/templates/ts/request.handlebars +++ b/src/templates/ts/request.handlebars @@ -4,10 +4,25 @@ let response: unknown; let response: any; {{/if}} +{{#if operation.responses}} + const responseSchema: { + [key: string]: { response: string | boolean | object | null; contentTypes: string[] | null; }; + } = {{json operation.responses}}; + let responseStatus: number; + let acceptedContentTypes: string[] | null; +{{/if}} +let responseContentType: string; + try { const [processedUrl, processedParams] = await config.preMiddleware(requestUrl, requestParams{{#if operation.header}} as RequestInit{{/if}}) const responseObject = await fetch(processedUrl, processedParams{{#if operation.header}} as RequestInit{{/if}}); - response = await responseObject.json(); + responseContentType = responseObject.headers.get('Content-Type') || 'application/json'; + + if (responseContentType.startsWith('application/json')) { + response = await responseObject.json(); + } else { + response = await responseObject.text(); + } if (!responseObject.ok) { throw new ApiError({ @@ -18,6 +33,14 @@ try { headers: responseObject.headers, }); } + {{#if operation.responses}} + responseStatus = responseObject.status; + acceptedContentTypes = responseSchema[responseStatus] && responseSchema[responseStatus].contentTypes; + + if (acceptedContentTypes && !acceptedContentTypes.includes(responseContentType)) { + throw new Error('Incorrect content type'); + } + {{/if}} } catch (error) { if (error instanceof ApiError) { throw error; @@ -26,24 +49,28 @@ try { } } -{{#if (and operation.response options.validateResponse)}} - const responseSchema = {{json operation.response}}; - +{{#if (and operation.responses options.validateResponse)}} function isResponse(obj: unknown): obj is {{capitalize operation.name}}Response { - const isValid = ajv.validate(responseSchema, obj); + const isValid = ajv.validate(responseSchema[responseStatus.toString()].response as string | boolean | object, obj); return !!isValid; } - if (!isResponse(response)) { - throw new ResponseValidationError({ - message: 'Response schema validation error', - method: '{{operation.name}}', - errors: ajv.errors || [], - }); + if (responseContentType.startsWith('application/json') + && responseSchema + && responseSchema[responseStatus.toString()] + && responseSchema[responseStatus.toString()].response) { + if (!isResponse(response)) { + throw new ResponseValidationError({ + message: 'Response schema validation error', + method: '{{operation.name}}', + errors: ajv.errors || [], + }); + } + return response; } - return response; + return response as {{capitalize operation.name}}Response; {{else}} return response as {{capitalize operation.name}}Response; {{/if}}