diff --git a/plugins/typescript/src/fixtures/petstore.ts b/plugins/typescript/src/fixtures/petstore.ts index 6e5e669b..c397079b 100644 --- a/plugins/typescript/src/fixtures/petstore.ts +++ b/plugins/typescript/src/fixtures/petstore.ts @@ -243,6 +243,16 @@ export const petstore: OpenAPIObject = { type: "string", }, }, + petFilterParam: { + name: "petFilter", + in: "query", + required: false, + schema: { + description: "Filter by type", + type: "string", + enum: ["cat", "dog"], + }, + }, }, requestBodies: { updatePetRequest: { @@ -255,6 +265,17 @@ export const petstore: OpenAPIObject = { }, required: true, }, + searchPetRequest: { + content: { + "application/json": { + schema: { + type: "string", + enum: ["cat", "dog"], + }, + }, + }, + required: true, + }, }, responses: { NotModified: { @@ -270,6 +291,17 @@ export const petstore: OpenAPIObject = { }, }, }, + PetTypeResponse: { + description: "Type of pet", + content: { + "application/json": { + schema: { + type: "string", + enum: ["cat", "dog"], + }, + }, + }, + }, }, schemas: { Pet: { diff --git a/plugins/typescript/src/generators/generateSchemaTypes.test.ts b/plugins/typescript/src/generators/generateSchemaTypes.test.ts index 64232dcb..46a0a2e7 100644 --- a/plugins/typescript/src/generators/generateSchemaTypes.test.ts +++ b/plugins/typescript/src/generators/generateSchemaTypes.test.ts @@ -326,6 +326,8 @@ describe("generateSchemaTypes", () => { export type NotModified = void; export type PetResponse = Schemas.Pet; + + export type PetTypeResponse = "cat" | "dog"; " `); }); @@ -356,6 +358,8 @@ describe("generateSchemaTypes", () => { import type * as Schemas from "./swaggerPetstoreSchemas"; export type UpdatePetRequest = Schemas.NewPet; + + export type SearchPetRequest = "cat" | "dog"; " `); }); @@ -386,6 +390,122 @@ describe("generateSchemaTypes", () => { * Unique identifier */ export type IdParam = string; + + /** + * Filter by type + */ + export type PetFilterParam = "cat" | "dog"; + " + `); + }); + + it("should generate the responses file with enums instead of string unions", async () => { + const writeFile = jest.fn(); + const readFile = jest.fn(() => Promise.resolve("")); + await generateSchemaTypes( + { + openAPIDocument: petstore, + writeFile, + readFile, + existsFile: () => true, + }, + { + filenameCase: "camel", + useEnums: true, + }, + ); + expect(writeFile.mock.calls[1][0]).toBe("swaggerPetstoreResponses.ts"); + expect(writeFile.mock.calls[1][1]).toMatchInlineSnapshot(` + "/** + * Generated by @openapi-codegen + * + * @version 1.0.0 + */ + import type * as Schemas from "./swaggerPetstoreSchemas"; + + export enum PetTypeResponse { + cat = "cat", + dog = "dog" + } + + export type NotModified = void; + + export type PetResponse = Schemas.Pet; + " + `); + }); + + it("should generate the request bodies file with enums instead of string unions", async () => { + const writeFile = jest.fn(); + const readFile = jest.fn(() => Promise.resolve("")); + await generateSchemaTypes( + { + openAPIDocument: petstore, + writeFile, + readFile, + existsFile: () => true, + }, + { + filenameCase: "camel", + useEnums: true, + }, + ); + expect(writeFile.mock.calls[2][0]).toBe( + "swaggerPetstoreRequestBodies.ts", + ); + expect(writeFile.mock.calls[2][1]).toMatchInlineSnapshot(` + "/** + * Generated by @openapi-codegen + * + * @version 1.0.0 + */ + import type * as Schemas from "./swaggerPetstoreSchemas"; + + export enum SearchPetRequest { + cat = "cat", + dog = "dog" + } + + export type UpdatePetRequest = Schemas.NewPet; + " + `); + }); + + it("should generate the parameters file with enums instead of string unions", async () => { + const writeFile = jest.fn(); + const readFile = jest.fn(() => Promise.resolve("")); + + await generateSchemaTypes( + { + openAPIDocument: petstore, + writeFile, + readFile, + existsFile: () => true, + }, + { + filenameCase: "camel", + useEnums: true, + }, + ); + expect(writeFile.mock.calls[3][0]).toBe("swaggerPetstoreParameters.ts"); + expect(writeFile.mock.calls[3][1]).toMatchInlineSnapshot(` + "/** + * Generated by @openapi-codegen + * + * @version 1.0.0 + */ + /** + * Filter by type + */ + export enum PetFilterParam { + cat = "cat", + dog = "dog" + } + + /** + * Unique identifier + */ + export type IdParam = string; " `); }); diff --git a/plugins/typescript/src/generators/generateSchemaTypes.ts b/plugins/typescript/src/generators/generateSchemaTypes.ts index 14a32de9..793b64bb 100644 --- a/plugins/typescript/src/generators/generateSchemaTypes.ts +++ b/plugins/typescript/src/generators/generateSchemaTypes.ts @@ -4,7 +4,10 @@ import ts from "typescript"; import { ReferenceObject, SchemaObject } from "openapi3-ts"; import { createWatermark } from "../core/createWatermark"; import { getUsedImports } from "../core/getUsedImports"; -import { schemaToTypeAliasDeclaration } from "../core/schemaToTypeAliasDeclaration"; +import { + OpenAPIComponentType, + schemaToTypeAliasDeclaration, +} from "../core/schemaToTypeAliasDeclaration"; import { getEnumProperties } from "../utils/getEnumProperties"; import { ConfigBase, Context } from "./types"; @@ -51,7 +54,8 @@ export const generateSchemaTypes = async ( .join("\n"); const handleTypeAlias = ( - componentSchema: [string, SchemaObject | ReferenceObject][] + componentSchema: [string, SchemaObject | ReferenceObject][], + currentComponent: OpenAPIComponentType ) => componentSchema.reduce( (mem, [name, schema]) => [ @@ -61,7 +65,7 @@ export const generateSchemaTypes = async ( schema, { openAPIDocument: context.openAPIDocument, - currentComponent: "schemas", + currentComponent: currentComponent, }, config.useEnums ), @@ -69,23 +73,10 @@ export const generateSchemaTypes = async ( [] ); - const filenamePrefix = - c.snake(config.filenamePrefix ?? context.openAPIDocument.info.title) + "-"; - - const formatFilename = config.filenameCase ? c[config.filenameCase] : c.camel; - const files = { - requestBodies: formatFilename(filenamePrefix + "-request-bodies"), - schemas: formatFilename(filenamePrefix + "-schemas"), - parameters: formatFilename(filenamePrefix + "-parameters"), - responses: formatFilename(filenamePrefix + "-responses"), - utils: formatFilename(filenamePrefix + "-utils"), - }; - - // Generate `components/schemas` types - if (components.schemas) { - const schemas: ts.Node[] = []; - const componentSchemaEntries = Object.entries(components.schemas); - + const generateTypeAliasDeclarations = ( + componentSchemaEntries: [string, SchemaObject | ReferenceObject][], + currentComponent: OpenAPIComponentType + ) => { if (config.useEnums) { const enumSchemaEntries = getEnumProperties(componentSchemaEntries); const enumSchemas = enumSchemaEntries.reduce( @@ -93,7 +84,7 @@ export const generateSchemaTypes = async ( ...mem, ...schemaToEnumDeclaration(name, schema, { openAPIDocument: context.openAPIDocument, - currentComponent: "schemas", + currentComponent, }), ], [] @@ -102,14 +93,35 @@ export const generateSchemaTypes = async ( const componentsSchemas = handleTypeAlias( componentSchemaEntries.filter( ([name]) => !enumSchemaEntries.some(([enumName]) => name === enumName) - ) + ), + currentComponent ); - schemas.push(...enumSchemas, ...componentsSchemas); + return [...enumSchemas, ...componentsSchemas]; } else { - const componentsSchemas = handleTypeAlias(componentSchemaEntries); - schemas.push(...componentsSchemas); + return handleTypeAlias(componentSchemaEntries, currentComponent); } + }; + + const filenamePrefix = + c.snake(config.filenamePrefix ?? context.openAPIDocument.info.title) + "-"; + + const formatFilename = config.filenameCase ? c[config.filenameCase] : c.camel; + const files = { + requestBodies: formatFilename(filenamePrefix + "-request-bodies"), + schemas: formatFilename(filenamePrefix + "-schemas"), + parameters: formatFilename(filenamePrefix + "-parameters"), + responses: formatFilename(filenamePrefix + "-responses"), + utils: formatFilename(filenamePrefix + "-utils"), + }; + + // Generate `components/schemas` types + if (components.schemas) { + const componentSchemaEntries = Object.entries(components.schemas); + const schemas = generateTypeAliasDeclarations( + componentSchemaEntries, + "schemas" + ); await context.writeFile( files.schemas + ".ts", @@ -123,28 +135,29 @@ export const generateSchemaTypes = async ( // Generate `components/responses` types if (components.responses) { - const componentsResponses = Object.entries(components.responses).reduce< - ts.Node[] - >((mem, [name, responseObject]) => { + // Convert responses to schemas + const componentsResponsesEntries = Object.entries( + components.responses + ).reduce<[string, SchemaObject][]>((mem, [name, responseObject]) => { if (isReferenceObject(responseObject)) return mem; const mediaType = findCompatibleMediaType(responseObject); - return [ - ...mem, - ...schemaToTypeAliasDeclaration(name, mediaType?.schema || {}, { - openAPIDocument: context.openAPIDocument, - currentComponent: "responses", - }), - ]; + mem.push([name, mediaType?.schema || {}]); + return mem; }, []); - if (componentsResponses.length) { + const schemas = generateTypeAliasDeclarations( + componentsResponsesEntries, + "responses" + ); + + if (schemas.length) { await context.writeFile( files.responses + ".ts", printNodes([ createWatermark(context.openAPIDocument.info), - ...getUsedImports(componentsResponses, files).nodes, - ...componentsResponses, + ...getUsedImports(schemas, files).nodes, + ...schemas, ]) ); } @@ -152,29 +165,30 @@ export const generateSchemaTypes = async ( // Generate `components/requestBodies` types if (components.requestBodies) { - const componentsRequestBodies = Object.entries( + // Convert requestBodies to schemas + const componentsRequestBodiesEntries = Object.entries( components.requestBodies - ).reduce((mem, [name, requestBodyObject]) => { + ).reduce<[string, SchemaObject][]>((mem, [name, requestBodyObject]) => { if (isReferenceObject(requestBodyObject)) return mem; const mediaType = findCompatibleMediaType(requestBodyObject); if (!mediaType || !mediaType.schema) return mem; - return [ - ...mem, - ...schemaToTypeAliasDeclaration(name, mediaType.schema, { - openAPIDocument: context.openAPIDocument, - currentComponent: "requestBodies", - }), - ]; + mem.push([name, mediaType.schema]); + return mem; }, []); - if (componentsRequestBodies.length) { + const schemas = generateTypeAliasDeclarations( + componentsRequestBodiesEntries, + "requestBodies" + ); + + if (schemas.length) { await context.writeFile( files.requestBodies + ".ts", printNodes([ createWatermark(context.openAPIDocument.info), - ...getUsedImports(componentsRequestBodies, files).nodes, - ...componentsRequestBodies, + ...getUsedImports(schemas, files).nodes, + ...schemas, ]) ); } @@ -182,27 +196,27 @@ export const generateSchemaTypes = async ( // Generate `components/parameters` types if (components.parameters) { - const componentsParameters = Object.entries(components.parameters).reduce< - ts.Node[] - >((mem, [name, parameterObject]) => { - if (isReferenceObject(parameterObject) || !parameterObject.schema) { + // Convert parameters to schemas + const componentsParametersEntries = Object.entries( + components.parameters + ).reduce<[string, SchemaObject][]>((mem, [name, parameterObject]) => { + if (isReferenceObject(parameterObject) || !parameterObject.schema) return mem; - } - return [ - ...mem, - ...schemaToTypeAliasDeclaration(name, parameterObject.schema, { - openAPIDocument: context.openAPIDocument, - currentComponent: "parameters", - }), - ]; + mem.push([name, parameterObject.schema as SchemaObject]); + return mem; }, []); + const schemas = generateTypeAliasDeclarations( + componentsParametersEntries, + "parameters" + ); + await context.writeFile( files.parameters + ".ts", printNodes([ createWatermark(context.openAPIDocument.info), - ...getUsedImports(componentsParameters, files).nodes, - ...componentsParameters, + ...getUsedImports(schemas, files).nodes, + ...schemas, ]) ); }