From 17dac433cc496c209b2a27f81fd44d43b6b820d3 Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:02:25 +0100 Subject: [PATCH 1/2] [typescript-fetch] add oneOf string union & array support (#19909) --- .../openapitools/codegen/CodegenProperty.java | 20 +++-- .../openapitools/codegen/DefaultCodegen.java | 12 +++ .../TypeScriptFetchClientCodegen.java | 50 +++++++++-- .../TypeScriptReduxQueryClientCodegen.java | 11 +-- .../typescript-fetch/modelOneOf.mustache | 70 +++++++++++++-- .../resources/3_0/typescript-fetch/oneOf.yaml | 26 ++++++ .../builds/oneOf/.openapi-generator/FILES | 1 + .../builds/oneOf/apis/DefaultApi.ts | 27 ++++++ .../builds/oneOf/models/TestArrayResponse.ts | 89 +++++++++++++++++++ .../builds/oneOf/models/TestResponse.ts | 26 +++++- .../builds/oneOf/models/index.ts | 1 + 11 files changed, 306 insertions(+), 27 deletions(-) create mode 100644 samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java index dddec1c620c6..419de1ca8f03 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenProperty.java @@ -20,12 +20,7 @@ import lombok.Getter; import lombok.Setter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperties { /** @@ -960,6 +955,19 @@ public void setIsEnum(boolean isEnum) { this.isEnum = isEnum; } + @SuppressWarnings("unchecked") + public List getAllowableValuesList() { + return Optional.ofNullable(this.getAllowableValues()) + .map(allowableValues -> allowableValues.get("values")) + .flatMap(valuesList -> { + try { + return Optional.ofNullable((List)valuesList); + } catch (ClassCastException e) { + return Optional.empty(); + } + }) + .orElse(Collections.emptyList()); + } @Override public String toString() { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index d8b577c93cae..a4b8173b9b01 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -2707,6 +2707,11 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map "\"" + (String)enumName + "\"") + .collect(Collectors.joining(" | ")); + } + /** * Combines all previously-detected type entries for a schema with newly-discovered ones, to ensure * that schema for items like enum include all possible values. diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index 875c881a3970..2169c7a86253 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -780,16 +780,41 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) { } } } + + cm.hasStringOneOf = cm.oneOf.contains("string"); + cm.hasStringArrayOneOf = cm.oneOf.contains("Array"); + + cm.oneOfModels = Optional.ofNullable(cm.getComposedSchemas()) + .map(CodegenComposedSchemas::getOneOf) + .orElse(Collections.emptyList()).stream() + .filter(CodegenProperty::getIsModel) + .map(CodegenProperty::getBaseType) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(TreeSet::new)); + + cm.oneOfStringEnums = Optional.ofNullable(cm.getComposedSchemas()) + .map(CodegenComposedSchemas::getOneOf) + .orElse(Collections.emptyList()).stream() + .filter(CodegenProperty::getIsEnum) + .map(CodegenProperty::getAllowableValuesList) + .flatMap(Collection::stream) + .collect(Collectors.toCollection(TreeSet::new)); + + cm.oneOfArrays = Optional.ofNullable(cm.getComposedSchemas()) + .map(CodegenComposedSchemas::getOneOf) + .orElse(Collections.emptyList()).stream() + .filter(CodegenProperty::getIsArray) + .map(CodegenProperty::getComplexType) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(TreeSet::new)); + if (!cm.oneOf.isEmpty()) { // For oneOfs only import $refs within the oneOf - TreeSet oneOfRefs = new TreeSet<>(); - for (String im : cm.imports) { - if (cm.oneOf.contains(im)) { - oneOfRefs.add(im); - } - } - cm.imports = oneOfRefs; + cm.imports = cm.imports.stream() + .filter(im -> cm.oneOfModels.contains(im) || cm.oneOfArrays.contains(im)) + .collect(Collectors.toCollection(TreeSet::new)); } + return cm; } @@ -1472,6 +1497,16 @@ public String toString() { public class ExtendedCodegenModel extends CodegenModel { @Getter @Setter public Set modelImports = new TreeSet(); + + @Getter @Setter + public Set oneOfModels = new TreeSet<>(); + @Getter @Setter + public Set oneOfStringEnums = new TreeSet<>(); + @Getter @Setter + public Set oneOfArrays = new TreeSet<>(); + + public boolean hasStringOneOf; + public boolean hasStringArrayOneOf; public boolean isEntity; // Is a model containing an "id" property marked as isUniqueId public String returnPassthrough; public boolean hasReturnPassthroughVoid; @@ -1568,6 +1603,7 @@ public ExtendedCodegenModel(CodegenModel cm) { this.setItems(cm.getItems()); this.setAdditionalProperties(cm.getAdditionalProperties()); this.setIsModel(cm.getIsModel()); + this.setComposedSchemas(cm.getComposedSchemas()); } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java index 1b78a1cc4ebb..eb9f8d00582f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptReduxQueryClientCodegen.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.TreeSet; +import java.util.stream.Collectors; public class TypeScriptReduxQueryClientCodegen extends AbstractTypeScriptClientCodegen { @@ -145,13 +146,9 @@ public ModelsMap postProcessModels(ModelsMap objs) { } if (!cm.oneOf.isEmpty()) { // For oneOfs only import $refs within the oneOf - TreeSet oneOfRefs = new TreeSet<>(); - for (String im : cm.imports) { - if (cm.oneOf.contains(im)) { - oneOfRefs.add(im); - } - } - cm.imports = oneOfRefs; + cm.imports = cm.imports.stream() + .filter(cm.oneOf::contains) + .collect(Collectors.toCollection(TreeSet::new)); } } diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache index c02a0df9da73..61f1ba83cd7f 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache @@ -1,5 +1,5 @@ {{#hasImports}} -{{#oneOf}} +{{#imports}} import type { {{{.}}} } from './{{.}}{{importFileExtension}}'; import { instanceOf{{{.}}}, @@ -7,7 +7,7 @@ import { {{{.}}}FromJSONTyped, {{{.}}}ToJSON, } from './{{.}}{{importFileExtension}}'; -{{/oneOf}} +{{/imports}} {{/hasImports}} {{>modelOneOfInterfaces}} @@ -31,11 +31,40 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole } {{/discriminator}} {{^discriminator}} - {{#oneOf}} + {{#oneOfStringEnums}} + if (json === "{{{.}}}") { + return json; + } + {{/oneOfStringEnums}} + {{#hasStringOneOf}} + if (json instanceof String) { + return json as {{classname}}; + } + {{/hasStringOneOf}} + if (!(json instanceof Object)){ + return {} as any; + } + {{#hasStringArrayOneOf}} + if (Array.isArray(json) && json.every(item => item instanceof String)) { + return json as Array; + } + {{/hasStringArrayOneOf}} + {{#oneOfModels}} if (instanceOf{{{.}}}(json)) { return {{{.}}}FromJSONTyped(json, true); } - {{/oneOf}} + {{/oneOfModels}} + {{#oneOfArrays}} + {{#-first}} + if (Array.isArray(json) && json.every(item => item instanceof Object)) { + {{/-first}} + if (json.every(item => instanceOf{{{.}}}(item as Object))) { + return json.map(value => {{{.}}}FromJSONTyped(value, true)); + } + {{#-last}} + } + {{/-last}} + {{/oneOfArrays}} return {} as any; {{/discriminator}} @@ -61,11 +90,40 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis {{/discriminator}} {{^discriminator}} - {{#oneOf}} + {{#oneOfStringEnums}} + if (value === "{{{.}}}") { + return value; + } + {{/oneOfStringEnums}} + {{#hasStringOneOf}} + if (value instanceof String) { + return value; + } + {{/hasStringOneOf}} + if (!(value instanceof Object)){ + return {}; + } + {{#hasStringArrayOneOf}} + if (Array.isArray(value) && value.every(item => item instanceof String)) { + return value; + } + {{/hasStringArrayOneOf}} + {{#oneOfModels}} if (instanceOf{{{.}}}(value)) { return {{{.}}}ToJSON(value as {{{.}}}); } - {{/oneOf}} + {{/oneOfModels}} + {{#oneOfArrays}} + {{#-first}} + if (Array.isArray(value) && value.every(item => item instanceof Object)) { + {{/-first}} + if (value.every(item => instanceOf{{{.}}}(item as Object))) { + return value.map(value => {{{.}}}ToJSON(value as {{{.}}})); + } + {{#-last}} + } + {{/-last}} + {{/oneOfArrays}} return {}; {{/discriminator}} diff --git a/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneOf.yaml b/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneOf.yaml index 244139a28d36..1003b8ab8a86 100644 --- a/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneOf.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneOf.yaml @@ -15,12 +15,38 @@ paths: application/json: schema: $ref: '#/components/schemas/TestResponse' + /test-array: + get: + operationId: testArray + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestArrayResponse' components: schemas: + TestArrayResponse: + oneOf: + - type: array + items: + $ref: "#/components/schemas/TestA" + - type: array + items: + $ref: "#/components/schemas/TestB" + - type: array + items: + type: string TestResponse: oneOf: - $ref: "#/components/schemas/TestA" - $ref: "#/components/schemas/TestB" + - type: string + enum: + - StringEnum1 + - StringEnum2 + - type: string TestA: type: object properties: diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/.openapi-generator/FILES b/samples/client/petstore/typescript-fetch/builds/oneOf/.openapi-generator/FILES index 1e29c70aedaa..6bb15a3adfc7 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/.openapi-generator/FILES +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/.openapi-generator/FILES @@ -2,6 +2,7 @@ apis/DefaultApi.ts apis/index.ts index.ts models/TestA.ts +models/TestArrayResponse.ts models/TestB.ts models/TestResponse.ts models/index.ts diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/apis/DefaultApi.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/apis/DefaultApi.ts index 3383adf93dfd..f7e1936ab80c 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/apis/DefaultApi.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/apis/DefaultApi.ts @@ -15,9 +15,12 @@ import * as runtime from '../runtime'; import type { + TestArrayResponse, TestResponse, } from '../models/index'; import { + TestArrayResponseFromJSON, + TestArrayResponseToJSON, TestResponseFromJSON, TestResponseToJSON, } from '../models/index'; @@ -51,4 +54,28 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async testArrayRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/test-array`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => TestArrayResponseFromJSON(jsonValue)); + } + + /** + */ + async testArray(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.testArrayRaw(initOverrides); + return await response.value(); + } + } diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts new file mode 100644 index 000000000000..0c2949c2a04a --- /dev/null +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts @@ -0,0 +1,89 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * testing oneOf without discriminator + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import type { TestA } from './TestA'; +import { + instanceOfTestA, + TestAFromJSON, + TestAFromJSONTyped, + TestAToJSON, +} from './TestA'; +import type { TestB } from './TestB'; +import { + instanceOfTestB, + TestBFromJSON, + TestBFromJSONTyped, + TestBToJSON, +} from './TestB'; + +/** + * @type TestArrayResponse + * + * @export + */ +export type TestArrayResponse = Array | Array | Array; + +export function TestArrayResponseFromJSON(json: any): TestArrayResponse { + return TestArrayResponseFromJSONTyped(json, false); +} + +export function TestArrayResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): TestArrayResponse { + if (json == null) { + return json; + } + if (!(json instanceof Object)){ + return {} as any; + } + if (Array.isArray(json) && json.every(item => item instanceof String)) { + return json as Array; + } + if (Array.isArray(json) && json.every(item => item instanceof Object)) { + if (json.every(item => instanceOfTestA(item as Object))) { + return json.map(value => TestAFromJSONTyped(value, true)); + } + if (json.every(item => instanceOfTestB(item as Object))) { + return json.map(value => TestBFromJSONTyped(value, true)); + } + } + + return {} as any; +} + +export function TestArrayResponseToJSON(json: any): any { + return TestArrayResponseToJSONTyped(json, false); +} + +export function TestArrayResponseToJSONTyped(value?: TestArrayResponse | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + if (!(value instanceof Object)){ + return {}; + } + if (Array.isArray(value) && value.every(item => item instanceof String)) { + return value; + } + if (Array.isArray(value) && value.every(item => item instanceof Object)) { + if (value.every(item => instanceOfTestA(item as Object))) { + return value.map(value => TestAToJSON(value as TestA)); + } + if (value.every(item => instanceOfTestB(item as Object))) { + return value.map(value => TestBToJSON(value as TestB)); + } + } + + return {}; +} + diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts index e228a3e9f73d..2ef836bc3cdb 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts @@ -32,7 +32,7 @@ import { * * @export */ -export type TestResponse = TestA | TestB; +export type TestResponse = "StringEnum1" | "StringEnum2" | TestA | TestB | string; export function TestResponseFromJSON(json: any): TestResponse { return TestResponseFromJSONTyped(json, false); @@ -42,6 +42,18 @@ export function TestResponseFromJSONTyped(json: any, ignoreDiscriminator: boolea if (json == null) { return json; } + if (json === "StringEnum1") { + return json; + } + if (json === "StringEnum2") { + return json; + } + if (json instanceof String) { + return json as TestResponse; + } + if (!(json instanceof Object)){ + return {} as any; + } if (instanceOfTestA(json)) { return TestAFromJSONTyped(json, true); } @@ -61,6 +73,18 @@ export function TestResponseToJSONTyped(value?: TestResponse | null, ignoreDiscr return value; } + if (value === "StringEnum1") { + return value; + } + if (value === "StringEnum2") { + return value; + } + if (value instanceof String) { + return value; + } + if (!(value instanceof Object)){ + return {}; + } if (instanceOfTestA(value)) { return TestAToJSON(value as TestA); } diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/index.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/index.ts index 8da6963476f2..7ba8efbbf0ef 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/index.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/index.ts @@ -1,5 +1,6 @@ /* tslint:disable */ /* eslint-disable */ export * from './TestA'; +export * from './TestArrayResponse'; export * from './TestB'; export * from './TestResponse'; From 2dd6b47821ef5fd8a7aecea973f0db13aec66f42 Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Wed, 27 Nov 2024 03:44:39 +0100 Subject: [PATCH 2/2] [typescript-fetch] add oneOf string union & array support (#19909) --- .../languages/TypeScriptFetchClientCodegen.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index 2169c7a86253..f855abb36555 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -784,25 +784,23 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) { cm.hasStringOneOf = cm.oneOf.contains("string"); cm.hasStringArrayOneOf = cm.oneOf.contains("Array"); - cm.oneOfModels = Optional.ofNullable(cm.getComposedSchemas()) + List oneOfsList = Optional.ofNullable(cm.getComposedSchemas()) .map(CodegenComposedSchemas::getOneOf) - .orElse(Collections.emptyList()).stream() + .orElse(Collections.emptyList()); + + cm.oneOfModels = oneOfsList.stream() .filter(CodegenProperty::getIsModel) .map(CodegenProperty::getBaseType) .filter(Objects::nonNull) .collect(Collectors.toCollection(TreeSet::new)); - cm.oneOfStringEnums = Optional.ofNullable(cm.getComposedSchemas()) - .map(CodegenComposedSchemas::getOneOf) - .orElse(Collections.emptyList()).stream() + cm.oneOfStringEnums = oneOfsList.stream() .filter(CodegenProperty::getIsEnum) .map(CodegenProperty::getAllowableValuesList) .flatMap(Collection::stream) .collect(Collectors.toCollection(TreeSet::new)); - cm.oneOfArrays = Optional.ofNullable(cm.getComposedSchemas()) - .map(CodegenComposedSchemas::getOneOf) - .orElse(Collections.emptyList()).stream() + cm.oneOfArrays = oneOfsList.stream() .filter(CodegenProperty::getIsArray) .map(CodegenProperty::getComplexType) .filter(Objects::nonNull)