Skip to content

Add paths support #328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'prettier/@typescript-eslint'],
plugins: ['@typescript-eslint', 'prettier'],
parser: "@typescript-eslint/parser",
extends: [
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint",
],
plugins: ["@typescript-eslint", "prettier"],
rules: {
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'prettier/prettier': 'error',
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off",
"prettier/prettier": "error",
"prefer-const": "off",
},
env: {
jest: true,
Expand Down
34 changes: 32 additions & 2 deletions src/types/OpenAPI3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,44 @@ export interface OpenAPI3Schemas {
[key: string]: OpenAPI3SchemaObject | OpenAPI3Reference;
}

export interface OpenAPI3Paths {
[path: string]: {
[method: string]: OpenAPI3Response;
};
}

export interface OpenAPI3Response {
description?: string;
parameters?: OpenAPI3Parameter[];
responses: {
[statusCode: string]: OpenAPI3ResponseObject;
};
}

export interface OpenAPI3Parameter {
name: string;
description?: string;
required?: boolean;
in: "query" | "header" | "path" | "cookie";
schema: OpenAPI3SchemaObject | OpenAPI3Reference;
}

export interface OpenAPI3ResponseObject {
description?: string;
content: {
[contentType: string]: { schema: OpenAPI3SchemaObject | OpenAPI3Reference };
};
}

export interface OpenAPI3Components {
schemas: OpenAPI3Schemas;
responses?: OpenAPI3Schemas;
responses?: { [key: string]: OpenAPI3ResponseObject };
}

export interface OpenAPI3 {
openapi: string;
components: OpenAPI3Components;
paths?: OpenAPI3Paths; // technically required by spec, but this library tries to be lenient
components?: OpenAPI3Components;
[key: string]: any; // handle other properties beyond swagger-to-ts’ concern
}

Expand Down
131 changes: 105 additions & 26 deletions src/v3.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import propertyMapper from "./property-mapper";
import {
OpenAPI3,
OpenAPI3Components,
OpenAPI3Parameter,
OpenAPI3Paths,
OpenAPI3SchemaObject,
OpenAPI3Schemas,
SwaggerToTSOptions,
Expand Down Expand Up @@ -34,16 +35,14 @@ export default function generateTypesV3(
options?: SwaggerToTSOptions
): string {
const { rawSchema = false } = options || {};
let components: OpenAPI3Components;
let { paths = {}, components = { schemas: {} } } = input as OpenAPI3;

if (rawSchema) {
components = { schemas: input };
} else {
components = (input as OpenAPI3).components;

if (!components || !components.schemas) {
if (!input.components && !input.paths) {
throw new Error(
`⛔️ 'components' missing from schema https://swagger.io/specification`
`No components or paths found. Specify --raw-schema to load a raw schema.`
);
}
}
Expand Down Expand Up @@ -111,7 +110,7 @@ export default function generateTypesV3(
if (Array.isArray(node.items)) {
return tsTupleOf(node.items.map(transform));
} else {
return tsArrayOf(transform(node.items as any));
return tsArrayOf(node.items ? transform(node.items as any) : "any");
}
}
}
Expand Down Expand Up @@ -154,27 +153,107 @@ export default function generateTypesV3(
return output;
}

if (rawSchema) {
const schemas = createKeys(propertyMapped, Object.keys(propertyMapped));
function transformPaths(paths: OpenAPI3Paths): string {
let output = "";
Object.entries(paths).forEach(([path, methods]) => {
output += `"${path}": {\n`;
Object.entries(methods).forEach(([method, responses]) => {
if (responses.description) output += comment(responses.description);
output += `"${method}": {\n`;

// handle parameters
if (responses.parameters) {
output += `parameters: {\n`;
const allParameters: Record<
string,
Record<string, OpenAPI3Parameter>
> = {};
responses.parameters.forEach((p) => {
if (!allParameters[p.in]) allParameters[p.in] = {};
// TODO: handle $ref parameters
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parameters can be a $ref rather than an object. The first cut doesn’t handle that (yet)

if (p.name) {
allParameters[p.in][p.name] = p;
}
});

Object.entries(allParameters).forEach(([loc, locParams]) => {
output += `"${loc}": {\n`;
Object.entries(locParams).forEach(([paramName, paramProps]) => {
if (paramProps.description)
output += comment(paramProps.description);
output += `"${paramName}"${
paramProps.required === true ? "" : "?"
}: ${transform(paramProps.schema)};\n`;
});
output += `}\n`;
});
output += `}\n`;
}

// handle responses
output += `responses: {\n`;
Object.entries(responses.responses).forEach(
([statusCode, response]) => {
if (response.description) output += comment(response.description);
if (!response.content || !Object.keys(response.content).length) {
output += `"${statusCode}": any;\n`;
return;
}
output += `"${statusCode}": {\n`;
Object.entries(response.content).forEach(
([contentType, encodedResponse]) => {
output += `"${contentType}": ${transform(
encodedResponse.schema
)};\n`;
}
);
output += `}\n`;
}
);
output += `}\n`;
output += `}\n`;
});
output += `}\n`;
});
return output;
}

if (rawSchema) {
return `export interface schemas {
${schemas}
}`;
${createKeys(propertyMapped, Object.keys(propertyMapped))}
}`;
}

const schemas = `schemas: {
${createKeys(propertyMapped, Object.keys(propertyMapped))}
}`;

const responses = !components.responses
? ``
: `responses: {
${createKeys(components.responses, Object.keys(components.responses))}
}`;

// note: make sure that base-level schemas are required
return `export interface components {
${schemas}
${responses}
}`;
// now put everything together
let finalOutput = "";

// handle paths
if (Object.keys(paths).length) {
finalOutput += `export interface paths {
${transformPaths(paths)}
}

`;
}

finalOutput += "export interface components {\n";

// TODO: handle components.parameters
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also don’t yet handle components.parameters, which would be needed for $ref parameters (above)


if (Object.keys(propertyMapped).length) {
finalOutput += `schemas: {
${createKeys(propertyMapped, Object.keys(propertyMapped))}
}`;
}
if (components.responses && Object.keys(components.responses).length) {
finalOutput += `
responses: {
${createKeys(components.responses, Object.keys(components.responses))}
}`;
}

// close components wrapper
finalOutput += "\n}";

return finalOutput;
}
Loading