diff --git a/scripts/testCodeGen.ts b/scripts/testCodeGen.ts index e4259717..1e9492e0 100644 --- a/scripts/testCodeGen.ts +++ b/scripts/testCodeGen.ts @@ -35,6 +35,7 @@ const main = () => { }); Writer.generateSplitCode("test/api.test.domain/index.yml", "test/code/split"); + Writer.generateSplitCode("test/multi-type.test.domain/index.yml", "test/code/mulit-type-test.domain"); Writer.generateParameter("test/api.test.domain/index.yml", "test/code/parameter/api.test.domain.json"); Writer.generateParameter("test/infer.domain/index.yml", "test/code/parameter/infer.domain.json"); diff --git a/src/internal/OpenApiTools/toTypeNode.ts b/src/internal/OpenApiTools/toTypeNode.ts index e0f419ab..c4f92c2a 100644 --- a/src/internal/OpenApiTools/toTypeNode.ts +++ b/src/internal/OpenApiTools/toTypeNode.ts @@ -2,7 +2,6 @@ import DotProp from "dot-prop"; import ts from "typescript"; import type { OpenApi } from "../../types"; -import { UnsetTypeError } from "../Exception"; import { UnSupportError } from "../Exception"; import * as Logger from "../Logger"; import { Factory } from "../TsGenerator"; @@ -65,8 +64,12 @@ export const generateMultiTypeNode = ( typeNodes, }); } - // TODO Feature Development: Calculate intersection types - return factory.TypeNode.create({ type: "never" }); + /** + * If you see this comment and have an idea for an AnyOf type output, please submit an Issue. + */ + return factory.UnionTypeNode.create({ + typeNodes, + }); }; const nullable = (factory: Factory.Type, typeNode: ts.TypeNode, nullable: boolean): ts.TypeNode => { diff --git a/src/internal/ResolveReference/index.ts b/src/internal/ResolveReference/index.ts index 48d19a2f..16c04c30 100644 --- a/src/internal/ResolveReference/index.ts +++ b/src/internal/ResolveReference/index.ts @@ -9,7 +9,12 @@ export { OpenApi }; export type ObjectLike = { [key: string]: any }; -const escapeFromJsonCyclic = (obj: any) => JSON.parse(JSON.stringify(obj)); +const escapeFromJsonCyclic = (obj: any) => { + if (!obj) { + return obj; + } + return JSON.parse(JSON.stringify(obj)); +}; const isObject = (value: any): value is ObjectLike => { return !!value && value !== null && !Array.isArray(value) && typeof value === "object"; diff --git a/test/__tests__/__snapshots__/multi-type.test.domain.ts.snap b/test/__tests__/__snapshots__/multi-type.test.domain.ts.snap new file mode 100644 index 00000000..de7810b3 --- /dev/null +++ b/test/__tests__/__snapshots__/multi-type.test.domain.ts.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Multi Type apiClient 1`] = ` +"// +// Generated by @himenon/openapi-typescript-code-generator +// +// OpenApi : 3.0.1 +// +// License : MIT +// + + +import { Schemas } from \\"./types\\"; +export interface RequestBody$putAnyOf { + \\"application/json\\": Schemas.Cat | Schemas.Dog; +} +export interface RequestBody$patchOneOf { + \\"application/json\\": Schemas.Cat | Schemas.Dog; +} +export type RequestContentType$putAnyOf = keyof RequestBody$putAnyOf; +export interface Params$putAnyOf { + requestBody: RequestBody$putAnyOf[\\"application/json\\"]; +} +export type RequestContentType$patchOneOf = keyof RequestBody$patchOneOf; +export interface Params$patchOneOf { + requestBody: RequestBody$patchOneOf[\\"application/json\\"]; +} +export type HttpMethod = \\"GET\\" | \\"PUT\\" | \\"POST\\" | \\"DELETE\\" | \\"OPTIONS\\" | \\"HEAD\\" | \\"PATCH\\" | \\"TRACE\\"; +export interface ObjectLike { + [key: string]: any; +} +export interface QueryParameter { + value: any; + style?: \\"form\\" | \\"spaceDelimited\\" | \\"pipeDelimited\\" | \\"deepObject\\"; + explode: boolean; +} +export interface QueryParameters { + [key: string]: QueryParameter; +} +export type SuccessResponses = void; +export namespace ErrorResponse { + export type putAnyOf = void; + export type patchOneOf = void; +} +export interface ApiClient { + request: (httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise; +} +export class Client { + private baseUrl: string; + constructor(private apiClient: ApiClient, baseUrl: string) { this.baseUrl = baseUrl.replace(/\\\\/$/, \\"\\"); } + /** + * operationId: putAnyOf + * Request URI: /pets + */ + public async putAnyOf(params: Params$putAnyOf, option?: RequestOption): Promise { + const url = this.baseUrl + \`/pets\`; + const headers = { + \\"Content-Type\\": \\"application/json\\" + }; + return this.apiClient.request(\\"PUT\\", url, headers, params.requestBody, undefined, option); + } + /** + * operationId: patchOneOf + * Request URI: /pets + */ + public async patchOneOf(params: Params$patchOneOf, option?: RequestOption): Promise { + const url = this.baseUrl + \`/pets\`; + const headers = { + \\"Content-Type\\": \\"application/json\\" + }; + return this.apiClient.request(\\"PATCH\\", url, headers, params.requestBody, undefined, option); + } +} +" +`; + +exports[`Multi Type types 1`] = ` +"// +// Generated by @himenon/openapi-typescript-code-generator +// +// OpenApi : 3.0.1 +// +// License : MIT +// + + +export namespace Schemas { + export interface Pet { + pet_type: string; + } + export type Dog = Schemas.Pet & { + bark?: boolean; + breed?: \\"Dingo\\" | \\"Husky\\" | \\"Retriever\\" | \\"Shepherd\\"; + }; + export type Cat = Schemas.Pet & { + hunts?: boolean; + age?: number; + }; +} +" +`; diff --git a/test/__tests__/multi-type.test.domain.ts b/test/__tests__/multi-type.test.domain.ts new file mode 100644 index 00000000..22e36b83 --- /dev/null +++ b/test/__tests__/multi-type.test.domain.ts @@ -0,0 +1,18 @@ +import * as fs from "fs"; +import * as path from "path"; + +import * as Utils from "../utils"; + +describe("Multi Type", () => { + test("types", () => { + const generateCode = fs.readFileSync(path.join(__dirname, "../code/mulit-type-test.domain/types.ts"), { encoding: "utf-8" }); + const text = Utils.replaceVersionInfo(generateCode); + expect(text).toMatchSnapshot(); + }); + + test("apiClient", () => { + const generateCode = fs.readFileSync(path.join(__dirname, "../code/mulit-type-test.domain/apiClient.ts"), { encoding: "utf-8" }); + const text = Utils.replaceVersionInfo(generateCode); + expect(text).toMatchSnapshot(); + }); +}); diff --git a/test/api.test.domain/index.yml b/test/api.test.domain/index.yml index df504206..1b075c30 100644 --- a/test/api.test.domain/index.yml +++ b/test/api.test.domain/index.yml @@ -266,6 +266,7 @@ components: - type: string - type: number - type: boolean + responses: Continue: description: | @@ -413,7 +414,7 @@ paths: 200: description: Search Book Result content: - application/json: + application/json: schema: type: object properties: diff --git a/test/multi-type.test.domain/index.yml b/test/multi-type.test.domain/index.yml new file mode 100644 index 00000000..4aea5937 --- /dev/null +++ b/test/multi-type.test.domain/index.yml @@ -0,0 +1,71 @@ +openapi: 3.0.1 +info: + version: 1.0.0 + title: multi-type.test.domain + description: Library test schema + license: + name: MIT + +servers: + - url: "http://multi-type.test.domain/" + description: Development Environment + +components: + schemas: + Pet: + type: object + required: + - pet_type + properties: + pet_type: + type: string + discriminator: + propertyName: pet_type + Dog: + allOf: + - $ref: "#/components/schemas/Pet" + - type: object + # all other properties specific to a `Dog` + properties: + bark: + type: boolean + breed: + type: string + enum: [Dingo, Husky, Retriever, Shepherd] + Cat: + allOf: + - $ref: "#/components/schemas/Pet" + - type: object + properties: + hunts: + type: boolean + age: + type: integer + +paths: + /pets: + put: + operationId: putAnyOf + requestBody: + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/Cat" + - $ref: "#/components/schemas/Dog" + responses: + "200": + description: Updated + + patch: + operationId: patchOneOf + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/Cat" + - $ref: "#/components/schemas/Dog" + responses: + "200": + description: Updated