openapi-typescript
Version:
Generate TypeScript types from Swagger OpenAPI specs
137 lines (122 loc) • 5.3 kB
text/typescript
import path from "path";
import { bold, yellow } from "kleur";
import prettier from "prettier";
import parserTypescript from "prettier/parser-typescript";
import { URL } from "url";
import load, { resolveSchema, VIRTUAL_JSON_URL } from "./load";
import { swaggerVersion } from "./utils";
import { transformAll } from "./transform/index";
import { GlobalContext, OpenAPI2, OpenAPI3, SchemaObject, SwaggerToTSOptions } from "./types";
export * from "./types"; // expose all types to consumers
export const WARNING_MESSAGE = `/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
`;
/**
* This function is the entry to the program and allows the user to pass in a remote schema and/or local schema.
* The URL or schema and headers can be passed in either programtically and/or via the CLI.
* Remote schemas are fetched from a server that supplies JSON or YAML format via an HTTP GET request. File based schemas
* are loaded in via file path, most commonly prefixed with the file:// format. Alternatively, the user can pass in
* OpenAPI2 or OpenAPI3 schema objects that can be parsed directly by the function without reading the file system.
*
* Function overloading is utilized for generating stronger types for our different schema types and option types.
*
* @param {string} schema Root Swagger Schema HTTP URL, File URL, and/or JSON or YAML schema
* @param {SwaggerToTSOptions<typeof schema>} [options] Options to specify to the parsing system
* @return {Promise<string>} {Promise<string>} Parsed file schema
*/
async function openapiTS(
schema: string | OpenAPI2 | OpenAPI3 | Record<string, SchemaObject>,
options: SwaggerToTSOptions = {} as Partial<SwaggerToTSOptions>
): Promise<string> {
const ctx: GlobalContext = {
additionalProperties: options.additionalProperties || false,
auth: options.auth,
defaultNonNullable: options.defaultNonNullable || false,
formatter: options && typeof options.formatter === "function" ? options.formatter : undefined,
immutableTypes: options.immutableTypes || false,
rawSchema: options.rawSchema || false,
version: options.version || 3,
} as any;
// note: we may be loading many large schemas into memory at once; take care to reuse references without cloning
// 1. load schema
let rootSchema: Record<string, any> = {};
let external: Record<string, Record<string, any>> = {};
const allSchemas: Record<string, Record<string, any>> = {};
if (typeof schema === "string") {
const schemaURL = resolveSchema(schema);
if (options.silent === false) console.log(yellow(`🔭 Loading spec from ${bold(schemaURL.href)}…`));
await load(schemaURL, {
...ctx,
schemas: allSchemas,
rootURL: schemaURL, // as it crawls schemas recursively, it needs to know which is the root to resolve everything relative to
httpHeaders: options.httpHeaders,
httpMethod: options.httpMethod,
});
for (const k of Object.keys(allSchemas)) {
if (k === schemaURL.href) {
rootSchema = allSchemas[k];
} else {
external[k] = allSchemas[k];
}
}
} else {
await load(schema, {
...ctx,
schemas: allSchemas,
rootURL: new URL(VIRTUAL_JSON_URL),
httpHeaders: options.httpHeaders,
httpMethod: options.httpMethod,
});
for (const k of Object.keys(allSchemas)) {
if (k === VIRTUAL_JSON_URL) {
rootSchema = allSchemas[k];
} else {
external[k] = allSchemas[k];
}
}
}
// 2. generate raw output
let output = WARNING_MESSAGE;
// 2a. root schema
if (!options?.version && !ctx.rawSchema) ctx.version = swaggerVersion(rootSchema as any); // note: root version cascades down to all subschemas
const rootTypes = transformAll(rootSchema, { ...ctx });
for (const k of Object.keys(rootTypes)) {
if (typeof rootTypes[k] === "string") {
output += `export interface ${k} {\n ${rootTypes[k]}\n}\n\n`;
}
}
// 2b. external schemas (subschemas)
output += `export interface external {\n`;
const externalKeys = Object.keys(external);
externalKeys.sort((a, b) => a.localeCompare(b, "en", { numeric: true })); // sort external keys because they may have resolved in a different order each time
for (const subschemaURL of externalKeys) {
output += ` "${subschemaURL}": {\n`;
const subschemaTypes = transformAll(external[subschemaURL], { ...ctx, namespace: subschemaURL });
for (const k of Object.keys(subschemaTypes)) {
output += ` "${k}": {\n ${subschemaTypes[k]}\n }\n`;
}
output += ` }\n`;
}
output += `}\n\n`;
// 3. Prettify
let prettierOptions: prettier.Options = {
parser: "typescript",
plugins: [parserTypescript],
};
if (options && options.prettierConfig) {
try {
const userOptions = await prettier.resolveConfig(path.resolve(process.cwd(), options.prettierConfig));
prettierOptions = {
...(userOptions || {}),
...prettierOptions,
plugins: [...(prettierOptions.plugins as prettier.Plugin[]), ...((userOptions && userOptions.plugins) || [])],
};
} catch (err) {
console.error(`❌ ${err}`);
}
}
return prettier.format(output, prettierOptions);
}
export default openapiTS;