Skip to content

Change additionalProperties default #612

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
May 25, 2021
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ For anything more complicated, or for generating specs dynamically, you can also
| `--output [location]` | `-o` | (stdout) | Where should the output file be saved? |
| `--auth [token]` | | | (optional) Provide an auth token to be passed along in the request (only if accessing a private schema). |
| `--immutable-types` | | `false` | (optional) Generates immutable types (readonly properties and readonly array). |
| `--additional-properties` | `-ap` | `false` | (optional) Allow arbitrary properties for all schema objects without `additionalProperties: false` |
| `--prettier-config [location]` | | | (optional) Path to your custom Prettier configuration for output |
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |

Expand Down
26 changes: 19 additions & 7 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ const cli = meow(
$ openapi-typescript [input] [options]

Options
--help display this
--output, -o Specify output file (default: stdout)
--auth (optional) Provide an authentication token for private URL
--immutable-types (optional) Generates immutable types (readonly properties and readonly array)
--prettier-config (optional) specify path to Prettier config file
--raw-schema (optional) Read from raw schema instead of document
--version (optional) Schema version (must be present for raw schemas)
--help display this
--output, -o Specify output file (default: stdout)
--auth (optional) Provide an authentication token for private URL
--immutable-types, -it (optional) Generates immutable types (readonly properties and readonly array)
--additional-properties, -ap (optional) Allow arbitrary properties for all schema objects without "additionalProperties: false"
--prettier-config, -c (optional) specify path to Prettier config file
--raw-schema (optional) Parse as partial schema (raw components)
--version (optional) Force schema parsing version
`,
{
flags: {
Expand All @@ -31,9 +32,15 @@ Options
},
immutableTypes: {
type: "boolean",
alias: "it",
},
additionalProperties: {
type: "boolean",
alias: "ap",
},
prettierConfig: {
type: "string",
alias: "c",
},
rawSchema: {
type: "boolean",
Expand All @@ -58,6 +65,9 @@ async function main() {
if (output === "FILE") {
console.info(bold(`✨ openapi-typescript ${require("../package.json").version}`)); // only log if we’re NOT writing to stdout
}
if (cli.flags.rawSchema && !cli.flags.version) {
throw new Error(`--raw-schema requires --version flag`);
}

// 1. input
let spec = undefined;
Expand All @@ -73,6 +83,8 @@ async function main() {

// 2. generate schema (the main part!)
const result = openapiTS(spec, {
auth: cli.flags.auth,
additionalProperties: cli.flags.additionalProperties,
immutableTypes: cli.flags.immutableTypes,
prettierConfig: cli.flags.prettierConfig,
rawSchema: cli.flags.rawSchema,
Expand Down
28,108 changes: 14,850 additions & 13,258 deletions examples/stripe-openapi3.ts

Large diffs are not rendered by default.

31 changes: 18 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import prettier from "prettier";
import parserTypescript from "prettier/parser-typescript";
import { swaggerVersion } from "./utils";
import { transformAll } from "./transform/index";
import { OpenAPI2, OpenAPI3, SchemaObject, SwaggerToTSOptions } from "./types";
import { GlobalContext, OpenAPI2, OpenAPI3, SchemaObject, SwaggerToTSOptions } from "./types";
export * from "./types"; // expose all types to consumers

export const WARNING_MESSAGE = `/**
Expand All @@ -16,22 +16,27 @@ export const WARNING_MESSAGE = `/**

export default function openapiTS(
schema: OpenAPI2 | OpenAPI3 | Record<string, SchemaObject>,
options?: SwaggerToTSOptions
options: SwaggerToTSOptions = {}
): string {
// 1. determine version
const version = (options && options.version) || swaggerVersion(schema as OpenAPI2 | OpenAPI3);
// 1. set up context
const ctx: GlobalContext = {
auth: options.auth,
additionalProperties: options.additionalProperties || false,
formatter: typeof options.formatter === "function" ? options.formatter : undefined,
immutableTypes: options.immutableTypes || false,
rawSchema: options.rawSchema || false,
version: options.version || swaggerVersion(schema as OpenAPI2 | OpenAPI3),
} as any;

// 2. generate output
let output = `${WARNING_MESSAGE}
${transformAll(schema, {
formatter: options && typeof options.formatter === "function" ? options.formatter : undefined,
immutableTypes: (options && options.immutableTypes) || false,
rawSchema: options && options.rawSchema,
version,
})}
`;
let output = WARNING_MESSAGE;
const rootTypes = transformAll(schema, { ...ctx });
for (const k of Object.keys(rootTypes)) {
if (typeof rootTypes[k] !== "string") continue;
output += `export interface ${k} {\n ${rootTypes[k]}\n}\n\n`;
}

// 3. Prettify output
// 3. Prettify
let prettierOptions: prettier.Options = {
parser: "typescript",
plugins: [parserTypescript],
Expand Down
15 changes: 10 additions & 5 deletions src/transform/headers.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { HeaderObject, SchemaFormatter } from "../types";
import { GlobalContext, HeaderObject } from "../types";
import { comment, tsReadonly } from "../utils";
import { transformSchemaObj } from "./schema";

interface TransformHeadersOptions extends GlobalContext {
required: Set<string>;
}

export function transformHeaderObjMap(
headerMap: Record<string, HeaderObject>,
options: { formatter?: SchemaFormatter; immutableTypes: boolean; version: number }
options: TransformHeadersOptions
): string {
let output = "";

Object.entries(headerMap).forEach(([k, v]) => {
if (!v.schema) return;
for (const k of Object.keys(headerMap)) {
const v = headerMap[k];
if (!v.schema) continue;

if (v.description) output += comment(v.description);

const readonly = tsReadonly(options.immutableTypes);
const required = v.required ? "" : "?";

output += ` ${readonly}"${k}"${required}: ${transformSchemaObj(v.schema, options)}\n`;
});
}

return output;
}
150 changes: 61 additions & 89 deletions src/transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,165 +1,137 @@
import { OperationObject, PathItemObject, SchemaFormatter } from "../types";
import { GlobalContext, OperationObject, PathItemObject } from "../types";
import { comment, tsReadonly } from "../utils";
import { transformHeaderObjMap } from "./headers";
import { transformOperationObj } from "./operation";
import { transformPathsObj } from "./paths";
import { transformResponsesObj, transformRequestBodies } from "./responses";
import { transformRequestBodies } from "./request";
import { transformResponsesObj } from "./responses";
import { transformSchemaObjMap } from "./schema";

interface TransformOptions {
formatter?: SchemaFormatter;
immutableTypes: boolean;
rawSchema?: boolean;
version: number;
}

export function transformAll(schema: any, { formatter, immutableTypes, rawSchema, version }: TransformOptions): string {
const readonly = tsReadonly(immutableTypes);
export function transformAll(schema: any, ctx: GlobalContext): Record<string, string> {
const readonly = tsReadonly(ctx.immutableTypes);

let output = "";
let output: Record<string, string> = {};

let operations: Record<string, { operation: OperationObject; pathItem: PathItemObject }> = {};

// --raw-schema mode
if (rawSchema) {
switch (version) {
if (ctx.rawSchema) {
const required = new Set(Object.keys(schema));
switch (ctx.version) {
case 2: {
return `export interface definitions {\n ${transformSchemaObjMap(schema, {
formatter,
immutableTypes,
required: Object.keys(schema),
version,
})}\n}`;
output.definitions = transformSchemaObjMap(schema, { ...ctx, required });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing transformAll()’s default output to be an object rather than a string is required for the upcoming #602. But I wanted to get that change in before we release v4, so that v3 patches will be easier to work on (less codebase distance between v3 and v4)

return output;
}
case 3: {
return `export interface schemas {\n ${transformSchemaObjMap(schema, {
formatter,
immutableTypes,
required: Object.keys(schema),
version,
})}\n }\n\n`;
output.schemas = transformSchemaObjMap(schema, { ...ctx, required });
return output;
}
}
}

// #/paths (V2 & V3)
output += `export interface paths {\n`; // open paths
output.paths = ""; // open paths
if (schema.paths) {
output += transformPathsObj(schema.paths, {
output.paths += transformPathsObj(schema.paths, {
...ctx,
globalParameters: (schema.components && schema.components.parameters) || schema.parameters,
immutableTypes,
operations,
version,
});
}
output += `}\n\n`; // close paths

switch (version) {
switch (ctx.version) {
case 2: {
// #/definitions
if (schema.definitions) {
output += `export interface definitions {\n ${transformSchemaObjMap(schema.definitions, {
formatter,
immutableTypes,
required: Object.keys(schema.definitions),
version,
})}\n}\n\n`;
output.definitions = transformSchemaObjMap(schema.definitions, {
...ctx,
required: new Set(Object.keys(schema.definitions)),
});
}

// #/parameters
if (schema.parameters) {
const required = Object.keys(schema.parameters);
output += `export interface parameters {\n ${transformSchemaObjMap(schema.parameters, {
formatter,
immutableTypes,
required,
version,
})}\n }\n\n`;
output.parameters = transformSchemaObjMap(schema.parameters, {
...ctx,
required: new Set(Object.keys(schema.parameters)),
});
}

// #/parameters
if (schema.responses) {
output += `export interface responses {\n ${transformResponsesObj(schema.responses, {
formatter,
immutableTypes,
version,
})}\n }\n\n`;
output.responses = transformResponsesObj(schema.responses, ctx);
}
break;
}
case 3: {
// #/components
output += `export interface components {\n`; // open components
output.components = "";

if (schema.components) {
// #/components/schemas
if (schema.components.schemas) {
const required = Object.keys(schema.components.schemas);
output += ` ${readonly}schemas: {\n ${transformSchemaObjMap(schema.components.schemas, {
formatter,
immutableTypes,
required,
version,
output.components += ` ${readonly}schemas: {\n ${transformSchemaObjMap(schema.components.schemas, {
...ctx,
required: new Set(Object.keys(schema.components.schemas)),
})}\n }\n`;
}

// #/components/responses
if (schema.components.responses) {
output += ` ${readonly}responses: {\n ${transformResponsesObj(schema.components.responses, {
formatter,
immutableTypes,
version,
})}\n }\n`;
output.components += ` ${readonly}responses: {\n ${transformResponsesObj(
schema.components.responses,
ctx
)}\n }\n`;
}

// #/components/parameters
if (schema.components.parameters) {
const required = Object.keys(schema.components.parameters);
output += ` ${readonly}parameters: {\n ${transformSchemaObjMap(schema.components.parameters, {
formatter,
immutableTypes,
required,
version,
output.components += ` ${readonly}parameters: {\n ${transformSchemaObjMap(schema.components.parameters, {
...ctx,
required: new Set(Object.keys(schema.components.parameters)),
})}\n }\n`;
}

// #/components/requestBodies
if (schema.components.requestBodies) {
output += ` ${readonly}requestBodies: {\n ${transformRequestBodies(schema.components.requestBodies, {
formatter,
immutableTypes,
version,
})}\n }\n`;
output.components += ` ${readonly}requestBodies: {\n ${transformRequestBodies(
schema.components.requestBodies,
ctx
)}\n }\n`;
}

// #/components/headers
if (schema.components.headers) {
output += ` ${readonly}headers: {\n ${transformHeaderObjMap(schema.components.headers, {
formatter,
immutableTypes,
version,
})} }\n`;
output.components += ` ${readonly}headers: {\n ${transformHeaderObjMap(schema.components.headers, {
...ctx,
required: new Set<string>(),
})}\n }\n`;
}
}

output += `}\n\n`; // close components
break;
}
}

output += `export interface operations {\n`; // open operations
// #/operations
output.operations = "";
if (Object.keys(operations).length) {
Object.entries(operations).forEach(([operationId, { operation, pathItem }]) => {
if (operation.description) output += comment(operation.description); // handle comment
output += ` ${readonly}"${operationId}": {\n ${transformOperationObj(operation, {
for (const id of Object.keys(operations)) {
const { operation, pathItem } = operations[id];
if (operation.description) output.operations += comment(operation.description); // handle comment
output.operations += ` ${readonly}"${id}": {\n ${transformOperationObj(operation, {
...ctx,
pathItem,
globalParameters: (schema.components && schema.components.parameters) || schema.parameters,
immutableTypes,
version,
})}\n }\n`;
});
}
}

// cleanup: trim whitespace
for (const k of Object.keys(output)) {
if (typeof output[k] === "string") {
output[k] = output[k].trim();
}
}
output += `}\n`; // close operations

return output.trim();
return output;
}
Loading