From 80e2602b4dc93688b21bf963e4e309248a41289a Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 2 Oct 2026 11:33:15 +0200 Subject: [PATCH] fix(typescript-fetch): preserve nullable primitive oneOf branches --- .../TypeScriptFetchClientCodegen.java | 5 ++ .../TypeScriptFetchClientCodegenTest.java | 33 ++++++++++++ .../oneof-nullable-primitives.yaml | 50 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneof-nullable-primitives.yaml 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 411521087608..da4471838f8f 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 @@ -965,6 +965,11 @@ private ExtendedCodegenModel processCodeGenModel(ExtendedCodegenModel cm) { .map(CodegenComposedSchemas::getOneOf) .orElse(Collections.emptyList()); + // Type names omit branch nullability. Null is valid only when exactly one branch accepts it. + if (oneOfsList.stream().filter(cp -> cp.isNullable).count() == 1) { + cm.oneOf.add("null"); + } + // create a set of any non-primitive, non-array types used in the oneOf schemas which will // need to be imported. cm.oneOfModels = oneOfsList.stream() 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 bd92f9d4617d..e325ecc573d8 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 @@ -17,6 +17,7 @@ import org.openapitools.codegen.typescript.TypeScriptGroups; import org.openapitools.codegen.utils.ModelUtils; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -752,6 +753,38 @@ public void testOneOfModelsDoNotImportPrimitiveTypes() throws IOException { TestUtils.assertFileContains(testDiscriminatorResponse, "export type TestDiscriminatorResponse = { discriminatorField: 'optionOne' } & OptionOne | { discriminatorField: 'optionTwo' } & OptionTwo"); } + @DataProvider + public Object[][] nullableOneOfRuntimeCheckModes() { + return new Object[][] {{false}, {true}}; + } + + @Test(dataProvider = "nullableOneOfRuntimeCheckModes") + public void testNullablePrimitiveOneOfTypes(boolean withoutRuntimeChecks) throws IOException { + Map properties = new HashMap<>(); + properties.put(TypeScriptFetchClientCodegen.WITHOUT_RUNTIME_CHECKS, withoutRuntimeChecks); + File output = generate(properties, + "src/test/resources/3_0/typescript-fetch/oneof-nullable-primitives.yaml"); + + for (String model : List.of("InlineNullableValue", "ReferencedNullableValue")) { + Path file = Paths.get(output + (withoutRuntimeChecks ? "/models/index.ts" : "/models/" + model + ".ts")); + TestUtils.assertFileContains(file, "export type " + model + " = null | number | string;"); + } + + // Null must match exactly one branch, rather than none or both branches. + for (String model : List.of("NonNullableValue", "MultipleNullableValue")) { + Path file = Paths.get(output + (withoutRuntimeChecks ? "/models/index.ts" : "/models/" + model + ".ts")); + TestUtils.assertFileContains(file, "export type " + model + " = number | string;"); + } + + Path arrayItems = Paths.get(output + (withoutRuntimeChecks + ? "/models/index.ts" : "/models/NullableArrayItems.ts")); + TestUtils.assertFileContains(arrayItems, "export type NullableArrayItems = Array | number;"); + + Path values = Paths.get(output + (withoutRuntimeChecks ? "/models/index.ts" : "/models/Values.ts")); + TestUtils.assertFileContains(values, "array: Array;", + "map: { [key: string]: InlineNullableValue; };"); + } + /** * Issue #21587 * When using oneOf, the Typescript Fetch generator should import modelled types except for diff --git a/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneof-nullable-primitives.yaml b/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneof-nullable-primitives.yaml new file mode 100644 index 000000000000..0a6e3e35253f --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneof-nullable-primitives.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.3 +info: + title: Nullable primitive oneOf values + version: 1.0.0 +paths: {} +components: + schemas: + NullableString: + type: string + nullable: true + InlineNullableValue: + oneOf: + - type: string + nullable: true + - type: number + ReferencedNullableValue: + oneOf: + - $ref: '#/components/schemas/NullableString' + - type: number + NonNullableValue: + oneOf: + - type: string + - type: number + MultipleNullableValue: + oneOf: + - type: string + nullable: true + - type: number + nullable: true + NullableArrayItems: + oneOf: + - type: array + items: + type: string + nullable: true + - type: number + Values: + type: object + required: + - array + - map + properties: + array: + type: array + items: + $ref: '#/components/schemas/InlineNullableValue' + map: + type: object + additionalProperties: + $ref: '#/components/schemas/InlineNullableValue'