Skip to content

Commit

Permalink
feat(ts-template): handle multiple status codes in responses
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandryackovlev committed Jan 18, 2020
1 parent d39292f commit be3995c
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 41 deletions.
68 changes: 39 additions & 29 deletions src/compile/ts/compileOperations.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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(),
Expand Down Expand Up @@ -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;
}),
Expand Down
51 changes: 39 additions & 12 deletions src/templates/ts/request.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof response>({
Expand All @@ -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;
Expand All @@ -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}}
Expand Down

0 comments on commit be3995c

Please sign in to comment.