From 3f3e9573a8ed8133d66e5b219acb5aa43655437c Mon Sep 17 00:00:00 2001 From: Sam Mesterton-Gibbons Date: Thu, 29 Oct 2020 12:45:36 +0000 Subject: [PATCH 1/3] Handle parameters on paths as well as methods Factors out generating the parameters block into a `parseParameters` function. Calls it both on operations, and also on the parameters block at the path level if one was given. Prevents the parameters block being parsed as a method. Fixes #346 --- src/types/OpenAPI3.ts | 2 +- src/v3.ts | 180 +++++++++++++++++++++++------------------- 2 files changed, 101 insertions(+), 81 deletions(-) diff --git a/src/types/OpenAPI3.ts b/src/types/OpenAPI3.ts index 6670d2d9e..b609423ef 100644 --- a/src/types/OpenAPI3.ts +++ b/src/types/OpenAPI3.ts @@ -10,7 +10,7 @@ export interface OpenAPI3Schemas { export interface OpenAPI3Paths { [path: string]: { - [method: string]: OpenAPI3Operation; + [method: string]: OpenAPI3Operation | Parameter[]; }; } diff --git a/src/v3.ts b/src/v3.ts index 5b8030ae0..ca1f3a988 100644 --- a/src/v3.ts +++ b/src/v3.ts @@ -5,6 +5,8 @@ import { OpenAPI3Paths, OpenAPI3SchemaObject, OpenAPI3Schemas, + OpenAPI3Operation, + Parameter, SwaggerToTSOptions, } from "./types"; import { @@ -153,100 +155,118 @@ export default function generateTypesV3( return output; } + function transformParameters(parameters: Parameter[]): string { + const allParameters: Record< + string, + Record + > = {}; + + let output = `parameters: {\n`; + + parameters.forEach((p) => { + if ("$ref" in p) { + const referencedValue = (p.$ref + .substr(2) + .split("/") + .reduce( + (value, property) => value[property], + input + ) as unknown) as OpenAPI3Parameter; + + if (!allParameters[referencedValue.in]) + allParameters[referencedValue.in] = {}; + + allParameters[referencedValue.in][ + referencedValue.name + ] = transformRef(p.$ref); + return; + } + + if (!allParameters[p.in]) allParameters[p.in] = {}; + allParameters[p.in][p.name] = p; + }); + + Object.entries(allParameters).forEach(([loc, locParams]) => { + output += `"${loc}": {\n`; + Object.entries(locParams).forEach(([paramName, paramProps]) => { + if (typeof paramProps === "string") { + output += `"${paramName}": ${paramProps}\n`; + return; + } + if (paramProps.description) + output += comment(paramProps.description); + output += `"${paramName}"${ + paramProps.required === true ? "" : "?" + }: ${transform(paramProps.schema)};\n`; + }); + output += `}\n`; + }); + output += `}\n`; + + return output; + } + function transformPaths(paths: OpenAPI3Paths): string { let output = ""; Object.entries(paths).forEach(([path, methods]) => { output += `"${path}": {\n`; - Object.entries(methods).forEach(([method, operation]) => { - if (operation.description) output += comment(operation.description); - output += `"${method}": {\n`; - - // handle parameters - if (operation.parameters) { - output += `parameters: {\n`; - const allParameters: Record< - string, - Record - > = {}; - operation.parameters.forEach((p) => { - if ("$ref" in p) { - const referencedValue = (p.$ref - .substr(2) - .split("/") - .reduce( - (value, property) => value[property], - input - ) as unknown) as OpenAPI3Parameter; - - if (!allParameters[referencedValue.in]) - allParameters[referencedValue.in] = {}; - - allParameters[referencedValue.in][ - referencedValue.name - ] = transformRef(p.$ref); - return; - } - if (!allParameters[p.in]) allParameters[p.in] = {}; - allParameters[p.in][p.name] = p; - }); + Object.entries(methods).forEach(([method, operation]) => { + // skip the parameters "method" for shared parameters - we'll handle it later + if (method !== "parameters") { + operation = operation as OpenAPI3Operation; + if (operation.description) output += comment(operation.description); + output += `"${method}": {\n`; + + // handle operation parameters + if (operation.parameters) { + output += transformParameters(operation.parameters); + } - Object.entries(allParameters).forEach(([loc, locParams]) => { - output += `"${loc}": {\n`; - Object.entries(locParams).forEach(([paramName, paramProps]) => { - if (typeof paramProps === "string") { - output += `"${paramName}": ${paramProps}\n`; - return; + // handle requestBody + if (operation.requestBody) { + output += `requestBody: {\n`; + Object.entries(operation.requestBody.content).forEach( + ([contentType, { schema }]) => { + output += `"${contentType}": ${transform(schema)};\n`; } - if (paramProps.description) - output += comment(paramProps.description); - output += `"${paramName}"${ - paramProps.required === true ? "" : "?" - }: ${transform(paramProps.schema)};\n`; - }); + ); output += `}\n`; - }); - output += `}\n`; - } + } - // handle requestBody - if (operation.requestBody) { - output += `requestBody: {\n`; - Object.entries(operation.requestBody.content).forEach( - ([contentType, { schema }]) => { - output += `"${contentType}": ${transform(schema)};\n`; + // handle responses + output += `responses: {\n`; + Object.entries(operation.responses).forEach( + ([statusCode, response]) => { + if (response.description) output += comment(response.description); + if (!response.content || !Object.keys(response.content).length) { + const type = + statusCode === "204" || Math.floor(+statusCode / 100) === 3 + ? "never" + : "unknown"; + output += `"${statusCode}": ${type};\n`; + return; + } + output += `"${statusCode}": {\n`; + Object.entries(response.content).forEach( + ([contentType, encodedResponse]) => { + output += `"${contentType}": ${transform( + encodedResponse.schema + )};\n`; + } + ); + output += `}\n`; } ); output += `}\n`; + output += `}\n`; } - - // handle responses - output += `responses: {\n`; - Object.entries(operation.responses).forEach( - ([statusCode, response]) => { - if (response.description) output += comment(response.description); - if (!response.content || !Object.keys(response.content).length) { - const type = - statusCode === "204" || Math.floor(+statusCode / 100) === 3 - ? "never" - : "unknown"; - output += `"${statusCode}": ${type};\n`; - return; - } - output += `"${statusCode}": {\n`; - Object.entries(response.content).forEach( - ([contentType, encodedResponse]) => { - output += `"${contentType}": ${transform( - encodedResponse.schema - )};\n`; - } - ); - output += `}\n`; - } - ); - output += `}\n`; - output += `}\n`; }); + + if (methods.parameters) { + // Handle shared parameters + output += transformParameters(methods.parameters as Parameter[]) + } output += `}\n`; }); return output; From c4cdb50ecec2c048bc07fa3bdf9e0e17ad271203 Mon Sep 17 00:00:00 2001 From: Sam Mesterton-Gibbons Date: Thu, 29 Oct 2020 15:05:38 +0000 Subject: [PATCH 2/3] Fix linting --- src/v3.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/v3.ts b/src/v3.ts index ca1f3a988..23cbc182a 100644 --- a/src/v3.ts +++ b/src/v3.ts @@ -176,9 +176,9 @@ export default function generateTypesV3( if (!allParameters[referencedValue.in]) allParameters[referencedValue.in] = {}; - allParameters[referencedValue.in][ - referencedValue.name - ] = transformRef(p.$ref); + allParameters[referencedValue.in][referencedValue.name] = transformRef( + p.$ref + ); return; } @@ -193,8 +193,7 @@ export default function generateTypesV3( output += `"${paramName}": ${paramProps}\n`; return; } - if (paramProps.description) - output += comment(paramProps.description); + if (paramProps.description) output += comment(paramProps.description); output += `"${paramName}"${ paramProps.required === true ? "" : "?" }: ${transform(paramProps.schema)};\n`; @@ -265,7 +264,7 @@ export default function generateTypesV3( if (methods.parameters) { // Handle shared parameters - output += transformParameters(methods.parameters as Parameter[]) + output += transformParameters(methods.parameters as Parameter[]); } output += `}\n`; }); From 604a84687be4a14573f60ef2d4c2350f8dad2761 Mon Sep 17 00:00:00 2001 From: Sam Mesterton-Gibbons Date: Thu, 5 Nov 2020 14:48:38 +0000 Subject: [PATCH 3/3] Add a test to exercise fix for #346 Adds a test of specifying parameters on the path instead of each method. --- tests/v3/index.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/v3/index.test.ts b/tests/v3/index.test.ts index bc1517a5f..b3f505cd0 100644 --- a/tests/v3/index.test.ts +++ b/tests/v3/index.test.ts @@ -824,4 +824,46 @@ describe("OpenAPI3 features", () => { `) ); }); + it("parameters on entire path (#346)", () => { + const schema: OpenAPI3 = { + openapi: "3.0.1", + paths: { + "/{example}": { + get: { + responses: {}, + }, + parameters: [ + { + name: "example", + in: "path", + required: true, + schema: { + type: "string" + } + }, + ], + }, + }, + }; + + expect(swaggerToTS(schema)).toEqual( + format(` + export interface paths { + + "/{example}": { + get: { + responses: {}; + }; + parameters: { + path: { + example: string; + }; + }; + }; + } + + export interface components {} + `) + ); + }); });