From d7fa3fe881c57535f0745a48d5a29c95b238e99c Mon Sep 17 00:00:00 2001 From: akira-furukawa-stb Date: Tue, 21 May 2024 17:51:05 +0900 Subject: [PATCH] Added primitive type support to typescript's oneOf --- .../TypeScriptFetchClientCodegen.java | 76 +++++++++++++++++++ .../typescript-fetch/modelOneOf.mustache | 63 ++++++++++++++- 2 files changed, 135 insertions(+), 4 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 0b1883308223..f060d630abbe 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 @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; @@ -1422,6 +1423,16 @@ public boolean isDateTimeType() { return isDateTime && "Date".equals(dataType); } + // for oneOf in issues-12256 + public Mustache.Lambda skipPrimitiveImports; + public Mustache.Lambda generatePrimitiveFunctions; + public Mustache.Lambda addTypeGuardsForPrimitives; + public Mustache.Lambda transformArrayTypeName; + public Mustache.Lambda generateArrayFunctions; + public Mustache.Lambda extractArrayInnerType; + public Mustache.Lambda importNonPrimitiveArrays; + public Mustache.Lambda checkArrayInnerType; + public ExtendedCodegenModel(CodegenModel cm) { super(); @@ -1506,6 +1517,69 @@ public ExtendedCodegenModel(CodegenModel cm) { this.setItems(cm.getItems()); this.setAdditionalProperties(cm.getAdditionalProperties()); this.setIsModel(cm.getIsModel()); + this.skipPrimitiveImports = (fragment, writer) -> { + String content = fragment.execute().trim(); + if (Arrays.stream(primitiveTypes).anyMatch(type -> content.contains("from './" + type + "'")) || content.contains("Array<")) { + return; + } + writer.write(content); + }; + this.generatePrimitiveFunctions = (fragment, writer) -> { + String content = fragment.execute(); + if (Arrays.stream(primitiveTypes).anyMatch(s -> content.contains("function " + s + "FromJSONTyped"))) { + writer.write(content); + } + }; + this.addTypeGuardsForPrimitives = (fragment, writer) -> { + String content = fragment.execute().trim(); + if (Arrays.stream(primitiveTypes).noneMatch(s -> content.contains("instanceOf" + s))) { + String primitiveGuard = oneOf.stream().filter(s -> Arrays.stream(primitiveTypes).anyMatch(s::equals)).map(s -> "typeof value !== '" + s + "' && ").collect(Collectors.joining()); + writer.write(primitiveGuard + content); + }else{ + writer.write(content); + } + }; + this.transformArrayTypeName = (fragment, writer) -> { + String content = fragment.execute().trim(); + + if (content.startsWith("Array<")) { + String changedName = content.replace("Array<", "").replace(">", "") + "Array"; + writer.write(changedName); + } else { + writer.write(content); + } + }; + this.generateArrayFunctions = (fragment, writer) -> { + String content = fragment.execute(); + if (content.contains("Array<")) { + writer.write(content); + } + }; + this.extractArrayInnerType = (fragment, writer) -> { + String content = fragment.execute().trim(); + if (content.startsWith("Array<")) { + String changedName = content.replace("Array<", "").replace(">", ""); + writer.write(changedName); + } + }; + this.importNonPrimitiveArrays = (fragment, writer) -> { + String content = fragment.execute(); + if (content.contains("Array<") && Arrays.stream(primitiveTypes).noneMatch(s -> content.contains("Array<" + s))) { + String replaced = content.replace("Array<", "").replace(">", ""); + writer.write(replaced); + } + }; + this.checkArrayInnerType = (fragment, writer) -> { + String content = fragment.execute(); + if (content.contains("Array<")) { + String replaced = content.replace("Array<", "").replace(">", ""); + if (Arrays.stream(primitiveTypes).anyMatch(replaced::equals)) { + writer.write("typeof element === '" + replaced + "'"); + } else { + writer.write("instanceOf" + replaced + "(element)"); + } + } + }; } public Set getModelImports() { @@ -1551,6 +1625,8 @@ public String toString() { return sb.toString(); } + private final String[] primitiveTypes = new String[] { "string", "number", "boolean" }; + } } 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 88d7bbbc6fc1..e6f55ee9b77b 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,6 @@ {{#hasImports}} {{#oneOf}} +{{#skipPrimitiveImports}} import type { {{{.}}} } from './{{.}}{{importFileExtension}}'; import { instanceOf{{{.}}}, @@ -7,9 +8,21 @@ import { {{{.}}}FromJSONTyped, {{{.}}}ToJSON, } from './{{.}}{{importFileExtension}}'; +{{/skipPrimitiveImports}} {{/oneOf}} - {{/hasImports}} +{{#oneOf}} +{{#importNonPrimitiveArrays}} +import type { {{{.}}} } from './{{{.}}}{{importFileExtension}}'; +import { + instanceOf{{{.}}}, + {{{.}}}FromJSON, + {{{.}}}FromJSONTyped, + {{{.}}}ToJSON, +} from './{{{.}}}{{importFileExtension}}'; +{{/importNonPrimitiveArrays}} +{{/oneOf}} + {{>modelOneOfInterfaces}} export function {{classname}}FromJSON(json: any): {{classname}} { @@ -31,7 +44,7 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole } {{/discriminator}} {{^discriminator}} - return {{#oneOf}}{{{.}}}FromJSONTyped(json, true){{^-last}} || {{/-last}}{{/oneOf}}; + return {{#oneOf}}{{#transformArrayTypeName}}{{{.}}}{{/transformArrayTypeName}}FromJSONTyped(json, true){{^-last}} || {{/-last}}{{/oneOf}}; {{/discriminator}} } @@ -52,11 +65,53 @@ export function {{classname}}ToJSON(value?: {{classname}} | null): any { {{^discriminator}} {{#oneOf}} - if (instanceOf{{{.}}}(value)) { - return {{{.}}}ToJSON(value as {{{.}}}); + if ({{#addTypeGuardsForPrimitives}}instanceOf{{#transformArrayTypeName}}{{{.}}}{{/transformArrayTypeName}}{{/addTypeGuardsForPrimitives}}(value)) { + return {{#transformArrayTypeName}}{{{.}}}{{/transformArrayTypeName}}ToJSON(value as {{{.}}}); } {{/oneOf}} return {}; {{/discriminator}} } + +{{#oneOf}} +{{#generatePrimitiveFunctions}} +function {{{.}}}FromJSONTyped(json: any, ignoreDiscriminator: boolean): {{{.}}} | null { + return typeof json === '{{{.}}}' ? json : null; +} + +function instanceOf{{{.}}}(value: any): boolean { + return typeof value === '{{{.}}}'; +} + +function {{{.}}}ToJSON(value: {{{.}}}): any { + return value; +} + +{{#-last}} +export function instanceOf{{classname}}(value: any): boolean { + return {{#oneOf}}instanceOf{{#transformArrayTypeName}}{{{.}}}{{/transformArrayTypeName}}(value){{^-last}} || {{/-last}}{{#-last}};{{/-last}}{{/oneOf}} +} +{{/-last}} +{{/generatePrimitiveFunctions}} +{{/oneOf}} + +{{#oneOf}} +{{#generateArrayFunctions}} +function instanceOf{{#extractArrayInnerType}}{{{.}}}{{/extractArrayInnerType}}Array(value: any): boolean { + return Array.isArray(value) && value.every(element => {{#checkArrayInnerType}}{{{.}}}{{/checkArrayInnerType}}); +} + +function {{#extractArrayInnerType}}{{{.}}}{{/extractArrayInnerType}}ArrayFromJSONTyped(json: any, ignoreDiscriminator: boolean): {{{.}}} | null { + if (!instanceOf{{#extractArrayInnerType}}{{{.}}}{{/extractArrayInnerType}}Array(json)) { + return null; + } + return json; +} + +function {{#extractArrayInnerType}}{{{.}}}{{/extractArrayInnerType}}ArrayToJSON(value: {{{.}}}): any { + return value; +} + +{{/generateArrayFunctions}} +{{/oneOf}}