diff --git a/README.md b/README.md index 05ae312..b7287b1 100644 --- a/README.md +++ b/README.md @@ -102,10 +102,12 @@ See the bottom of the document for template variable definitions. export interface Config { /** * The path to the OpenAPI/Swagger schema file. + * @default './schema.json' */ input?: string /** * The path to the output directory. + * @default './src/apis/generated' */ output?: string /** @@ -114,28 +116,38 @@ export interface Config { base?: string /** * The filename of the generated openapi types file. + * @default '_types.ts' */ typesFilename?: string - /** - * The transformer api options, used to override the default transformation rules. - */ - transformer?: Partial /** * Whether to generate TypeScript code. + * @default true */ ts?: boolean /** * Whether to generate only types. + * @default false */ typesOnly?: boolean /** * Whether to override the existing files, or an array of filenames to override. + * @default true */ overrides?: boolean | string[] /** * The preset ejs template to use. + * @default 'axle' */ preset?: Preset + /** + * Defines which return status codes will be typed + * @default (status) => status >= 200 && status < 300 + */ + validateStatus?: (status: number) => boolean + /** + * The transformer api options, used to override the default transformation rules. + */ + transformer?: Partial } ``` diff --git a/src/generate.ts b/src/generate.ts index e56a14d..932a2e2 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -9,7 +9,7 @@ import { getConfig } from './config' import { CWD, SUPPORTED_HTTP_METHODS } from './constants' import { createTransformer, Transformer, TransformerBaseArgs } from './transformer' import { - getSuccessfulResponseMeme, + getValidResponseMetadataItems, hasQueryParameter, isRequiredRequestBody, Preset, @@ -105,10 +105,12 @@ export interface ApiModulePayload { export interface GenerateOptions { /** * The path to the OpenAPI/Swagger schema file. + * @default './schema.json' */ input?: string /** * The path to the output directory. + * @default './src/apis/generated' */ output?: string /** @@ -117,40 +119,50 @@ export interface GenerateOptions { base?: string /** * The filename of the generated openapi types file. + * @default '_types.ts' */ typesFilename?: string - /** - * The transformer api options, used to override the default transformation rules. - */ - transformer?: Partial /** * Whether to generate TypeScript code. + * @default true */ ts?: boolean /** * Whether to generate only types. + * @default false */ typesOnly?: boolean /** * Whether to override the existing files, or an array of filenames to override. + * @default true */ overrides?: boolean | string[] /** * The preset ejs template to use. + * @default 'axle' */ preset?: Preset + /** + * Defines which return status codes will be typed + * @default (status) => status >= 200 && status < 300 + */ + validateStatus?: (status: number) => boolean + /** + * The transformer api options, used to override the default transformation rules. + */ + transformer?: Partial } export function transformPayloads( pathItems: Record, options: { path: string - transformer: Transformer base: string | undefined + validateStatus: (status: number) => boolean }, ) { - const { transformer, path, base } = options + const { transformer, path, base, validateStatus } = options return Object.entries(pathItems) .filter(([key]) => SUPPORTED_HTTP_METHODS.includes(key)) .reduce((payloads, [method, operation]) => { @@ -180,11 +192,11 @@ export function transformPayloads( }) : 'undefined' - const { mime, statusCode } = getSuccessfulResponseMeme(operation) const typeResponseBody = transformer.typeResponseBody({ ...args, type, verb, entity }) + const responseMetadataItems = getValidResponseMetadataItems(operation, validateStatus) const typeResponseBodyValue = - mime && statusCode - ? transformer.typeResponseBodyValue({ ...args, type, verb, entity, statusCode, mime }) + responseMetadataItems.length > 0 + ? transformer.typeResponseBodyValue({ ...args, type, verb, entity, responseMetadataItems }) : 'undefined' payloads.push({ @@ -212,9 +224,10 @@ export function partitionApiModules( options: { transformer: Transformer base: string | undefined + validateStatus: (status: number) => boolean }, ): ApiModule[] { - const { base, transformer } = options + const { base, transformer, validateStatus } = options const schemaPaths = schema.paths ?? {} const schemaPathKeys = base ? Object.keys(schemaPaths).map((key) => key.replace(base, '')) : Object.keys(schemaPaths) @@ -223,7 +236,9 @@ export function partitionApiModules( const payloads = paths.reduce((payloads, path) => { const pathItems = schemaPaths[path] as Record - payloads.push(...transformPayloads(pathItems, { ...options, path: base ? base + path : path, transformer })) + payloads.push( + ...transformPayloads(pathItems, { ...options, path: base ? base + path : path, transformer, validateStatus }), + ) return payloads }, [] as ApiModulePayload[]) @@ -310,6 +325,7 @@ export async function generate(userOptions: GenerateOptions = {}) { input = './schema.json', output = './src/apis/generated', typesFilename = '_types.ts', + validateStatus = (status: number) => status >= 200 && status < 300, transformer = {}, } = options @@ -326,6 +342,7 @@ export async function generate(userOptions: GenerateOptions = {}) { const apiModules = partitionApiModules(schema, { base, transformer: mergedTransformer, + validateStatus, }) await renderApiModules(apiModules, { output, typesFilename, ts, typesOnly, overrides, preset }) diff --git a/src/transformer.ts b/src/transformer.ts index 25fe247..ab28c82 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -1,6 +1,7 @@ import { OperationObject } from 'openapi-typescript' import pluralize from 'pluralize' import { camelize, pascalCase } from 'rattail' +import { ResponseMetadataItem } from './utils' export type TransformerBaseArgs = { path: string @@ -94,16 +95,16 @@ export function transformTypeResponseBody({ export function transformTypeResponseBodyValue({ type, - statusCode, - mime, + responseMetadataItems, }: { type: string verb: string entity: string - statusCode: number - mime: string + responseMetadataItems: ResponseMetadataItem[] } & TransformerBaseArgs) { - return `${type}['responses']['${statusCode}']['content']['${mime}']` + return responseMetadataItems + .map(({ status, mime }) => `${type}['responses']['${status}']['content']['${mime}']`) + .join(' | ') } export interface Transformer { diff --git a/src/utils.ts b/src/utils.ts index 46e63af..704ceef 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -83,25 +83,29 @@ export function isRequiredRequestBody(value: RequestBodyObject | ReferenceObject return 'required' in value && value.required === true } -export function getSuccessfulResponseMeme(operation: OperationObject) { +export type ResponseMetadataItem = { status: number; mime: string } + +export function getValidResponseMetadataItems( + operation: OperationObject, + validateStatus: (status: number) => boolean, +): ResponseMetadataItem[] { const responses = operation.responses ?? {} - const codeKey = Object.keys(responses) + const validStatusResults = Object.keys(responses) .sort((a, b) => Number(a) - Number(b)) - .find((codeKey) => Number(codeKey) >= 200 && Number(codeKey) <= 299) + .filter((key) => validateStatus(Number(key))) + .map(Number) - if (!codeKey) { - return { - statusCode: undefined, - mime: undefined, - } - } + const results = validStatusResults + .map((status) => { + const content = (operation.responses?.[status] as ResponseObject | undefined)?.content + const mime = content?.['application/json'] ? 'application/json' : content?.['*/*'] ? '*/*' : undefined - const statusCode = Number(codeKey) - const content = (operation.responses?.[statusCode] as ResponseObject | undefined)?.content - const mime = content?.['application/json'] ? 'application/json' : content?.['*/*'] ? '*/*' : undefined + return { + status, + mime, + } + }) + .filter((result) => result.mime) as ResponseMetadataItem[] - return { - statusCode, - mime, - } + return results }