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 8b2ebbcf7f30..adf43d4a35f0 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 @@ -799,6 +799,11 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) { .filter(Objects::nonNull) .collect(Collectors.toCollection(TreeSet::new)); + cm.oneOfPrimitives = oneOfsList.stream() + .filter(CodegenProperty::getIsPrimitiveType) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(HashSet::new)); + if (!cm.oneOf.isEmpty()) { // For oneOfs only import $refs within the oneOf cm.imports = cm.imports.stream() @@ -1484,6 +1489,8 @@ public class ExtendedCodegenModel extends CodegenModel { public Set oneOfModels = new TreeSet<>(); @Getter @Setter public Set oneOfArrays = new TreeSet<>(); + @Getter @Setter + public Set oneOfPrimitives = new HashSet<>(); public boolean isEntity; // Is a model containing an "id" property marked as isUniqueId public String returnPassthrough; 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 1d35933047a3..e03ac587a6a1 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache @@ -64,7 +64,64 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole } {{/-last}} {{/oneOfArrays}} - + {{#oneOfPrimitives}} + {{#isArray}} + {{#items}} + {{#isDateType}} + if (Array.isArray(json)) { + if (json.every(item => !(isNaN(new Date(json).getTime()))) { + return json.map(value => new Date(json); + } + } + {{/isDateType}} + {{#isDateTimeType}} + if (Array.isArray(json)) { + if (json.every(item => !(isNaN(new Date(json).getTime()))) { + return json.map(value => new Date(json); + } + } + {{/isDateTimeType}} + {{#isNumeric}} + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return json; + } + } + {{/isNumeric}} + {{#isString}} + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}})) { + return json; + } + } + {{/isString}} + {{/items}} + {{/isArray}} + {{^isArray}} + {{#isDateType}} + if(!(isNaN(new Date(json).getTime()))) { + return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json)); + } + {{/isDateType}} + {{^isDateType}} + {{#isDateTimeType}} + if(!(isNaN(new Date(json).getTime()))) { + return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json)); + } + {{/isDateTimeType}} + {{/isDateType}} + {{#isNumeric}} + if(typeof json === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return json; + } + {{/isNumeric}} + {{#isString}} + if(typeof json === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}json === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return json; + } + {{/isString}} + {{/isArray}} + {{/oneOfPrimitives}} return {} as any; {{/discriminator}} } @@ -112,7 +169,62 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis } {{/-last}} {{/oneOfArrays}} - + {{#oneOfPrimitives}} + {{#isArray}} + {{#items}} + {{#isDateType}} + if (Array.isArray(value)) { + if (value.every(item => item instanceof Date) { + return value.map(value => value.toISOString().substring(0,10))); + } + } + {{/isDateType}} + {{#isDateTimeType}} + if (Array.isArray(value)) { + if (value.every(item => item instanceof Date) { + return value.map(value => value.toISOString(); + } + } + {{/isDateTimeType}} + {{#isNumeric}} + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + } + {{/isNumeric}} + {{#isString}} + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}item === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + } + {{/isString}} + {{/items}} + {{/isArray}} + {{^isArray}} + {{#isDateType}} + if(value instanceof Date) { + return ((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString().substring(0,10)); + } + {{/isDateType}} + {{#isDateTimeType}} + if(value instanceof Date) { + return {{^required}}{{#isNullable}}value === null ? null : {{/isNullable}}{{^isNullable}}value == null ? undefined : {{/isNullable}}{{/required}}((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString()); + } + {{/isDateTimeType}} + {{#isNumeric}} + if(typeof value === 'number'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === {{.}}{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + {{/isNumeric}} + {{#isString}} + if(typeof value === 'string'{{#isEnum}} && ({{#allowableValues}}{{#values}}value === '{{.}}'{{^-last}} || {{/-last}}{{/values}}{{/allowableValues}}){{/isEnum}}) { + return value; + } + {{/isString}} + {{/isArray}} + {{/oneOfPrimitives}} return {}; {{/discriminator}} } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java index d1a6d8e43016..a9ddd5677e90 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java @@ -372,6 +372,39 @@ public void givenObjectHasAdditionalPropertiesWhenGenerateThenIndexSignatureNotU TestUtils.assertFileContains(exampleApiPath, "new Blob([JSON.stringify(ResponseOfStringToJSON"); } + @Test(description = "Issue #21295") + public void givenSchemaIsOneOfAndComposedSchemasArePrimitiveThenReturnStatementsAreCorrect() throws Exception { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath(); + + + TypeScriptFetchClientCodegen clientCodegen = new TypeScriptFetchClientCodegen(); + clientCodegen.setOutputDir(outputPath); + + DefaultGenerator defaultGenerator = new DefaultGenerator(); + defaultGenerator.opts( + new ClientOptInput().openAPI(TestUtils.parseSpec("src/test/resources/bugs/issue_21259.yaml")) + .config(clientCodegen) + ).generate(); + + Path exampleModelPath = Paths.get(outputPath + "/models/MyCustomSpeed.ts"); + //FromJSON + TestUtils.assertFileContains(exampleModelPath, "typeof json === 'number'"); + TestUtils.assertFileContains(exampleModelPath, "typeof json === 'string'"); + TestUtils.assertFileContains(exampleModelPath, "json === 'fixed-value-a' || json === 'fixed-value-b' || json === 'fixed-value-c'"); + TestUtils.assertFileContains(exampleModelPath, "isNaN(new Date(json).getTime())"); + TestUtils.assertFileContains(exampleModelPath, "json.every(item => typeof item === 'number'"); +// TestUtils.assertFileContains(exampleModelPath, "json.every(item => typeof item === 'string' && (item === 'oneof-array-enum-a' || item oneof-array-enum-b || item === oneof-array-enum-c)"); + //ToJSON + TestUtils.assertFileContains(exampleModelPath, "typeof value === 'number'"); + TestUtils.assertFileContains(exampleModelPath, "typeof value === 'string'"); + TestUtils.assertFileContains(exampleModelPath, "value === 'fixed-value-a' || value === 'fixed-value-b' || value === 'fixed-value-c'"); + TestUtils.assertFileContains(exampleModelPath, "value instanceof Date"); + TestUtils.assertFileContains(exampleModelPath, "value.every(item => typeof item === 'number'"); +// TestUtils.assertFileContains(exampleModelPath, "value.every(item => typeof item === 'string' && (item === 'oneof-array-enum-a' || item oneof-array-enum-b || item === oneof-array-enum-c)"); + } + private static File generate(Map properties) throws IOException { File output = Files.createTempDirectory("test").toFile(); output.deleteOnExit(); diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_21259.yaml b/modules/openapi-generator/src/test/resources/bugs/issue_21259.yaml new file mode 100644 index 000000000000..d0d732e01411 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_21259.yaml @@ -0,0 +1,62 @@ +#Modified from the original +openapi: 3.0.1 +info: + title: Minimal API for Bug Report + version: v1 +paths: + /test: + post: + summary: Test endpoint with MyCustomSpeed + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestPayload' + responses: + '200': + description: OK +components: + schemas: + MyNumericValue: + type: object + properties: + lmnop: + type: number + description: A numeric value (e.g., 0 to 1). + MyCustomSpeed: + oneOf: + - $ref: '#/components/schemas/MyNumericValue' + - type: string + enum: + - "fixed-value-a" + - "fixed-value-b" + - "fixed-value-c" + - type: string + format: date + - type: string + format: date-time + - type: integer + format: int64 + enum: [10, 20, 30] + - type: array + items: + type: number + - type: array + items: + - type: object + - type: array + items: + - type: string + enum: + # It seems enums within arrays don't work. Leaving this here, though + - "oneof-array-enum-a" + - "oneof-array-enum-b" + - "oneof-array-enum-c" + - type: + description: A value that can be a number or a specific string. + TestPayload: + type: object + properties: + speed_setting: + $ref: '#/components/schemas/MyCustomSpeed' \ No newline at end of file diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts index 0848d99781c3..ed4b81245828 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestArrayResponse.ts @@ -53,7 +53,11 @@ export function TestArrayResponseFromJSONTyped(json: any, ignoreDiscriminator: b } return json; } - + if (Array.isArray(json)) { + if (json.every(item => typeof item === 'string')) { + return json; + } + } return {} as any; } @@ -76,7 +80,11 @@ export function TestArrayResponseToJSONTyped(value?: TestArrayResponse | null, i } return value; } - + if (Array.isArray(value)) { + if (value.every(item => typeof item === 'string') { + return value; + } + } 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 09d1da131560..466c781097c6 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/TestResponse.ts @@ -51,7 +51,9 @@ export function TestResponseFromJSONTyped(json: any, ignoreDiscriminator: boolea if (instanceOfTestB(json)) { return TestBFromJSONTyped(json, true); } - + if(typeof json === 'string') { + return json; + } return {} as any; } @@ -72,7 +74,9 @@ export function TestResponseToJSONTyped(value?: TestResponse | null, ignoreDiscr if (instanceOfTestB(value)) { return TestBToJSON(value as TestB); } - + if(typeof value === 'string') { + return value; + } return {}; }