From 5290a94efa4082fb0cebd6b2ff6ade4f690abd55 Mon Sep 17 00:00:00 2001 From: Denis Belyanski Date: Tue, 30 Jun 2026 11:31:25 +0300 Subject: [PATCH 1/5] [Kotlin-client] fix kotlin code client generator for oneOf with allOf --- .../openapitools/codegen/DefaultCodegen.java | 33 +++++++-- .../languages/AbstractKotlinCodegen.java | 1 + .../kotlin/KotlinClientCodegenModelTest.java | 35 ++++++++++ ...orphism-allof-and-oneof-discriminator.yaml | 69 +++++++++++++++++++ 4 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_1/polymorphism-allof-and-oneof-discriminator.yaml 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 006bdc827c08..eff3f8feea4f 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 @@ -278,6 +278,12 @@ apiTemplateFiles are for API outputs only (controllers/handlers). * keyword. For example, the Java code generator may generate 'extends HashMap'. */ protected boolean supportsInheritance; + /** + * True if the language generator should not merge oneOf children's properties + * into the parent schema when the parent has a discriminator. This prevents + * child-specific properties from leaking into the parent interface/class. + */ + protected boolean skipOneOfPropertyMergeInParent; /** * True if the language generator supports the 'additionalProperties' keyword * as sibling of a composed (allOf/anyOf/oneOf) schema. @@ -2741,6 +2747,10 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map()); + } else if (skipOneOfPropertyMergeInParent && composed.getOneOf() != null && composed.getDiscriminator() != null && supportsInheritance) { + // polymorphic parent with discriminator and oneOf children — + // these are type alternatives (subtypes), not compositions, + // so their properties should not be merged into the parent } else { // composition Map newProperties = new LinkedHashMap<>(); @@ -3737,15 +3751,20 @@ protected void addProperties(Map properties, List requir required.addAll(schema.getRequired()); } - if (schema.getOneOf() != null) { - for (Object component : schema.getOneOf()) { - addProperties(properties, required, (Schema) component, visitedSchemas); + // Note: oneOf and anyOf represent type alternatives, not compositions. + // When skipOneOfPropertyMergeInParent is enabled, their children's properties + // should NOT be merged into the parent schema. + if (!skipOneOfPropertyMergeInParent) { + if (schema.getOneOf() != null) { + for (Object component : schema.getOneOf()) { + addProperties(properties, required, (Schema) component, visitedSchemas); + } } - } - if (schema.getAnyOf() != null) { - for (Object component : schema.getAnyOf()) { - addProperties(properties, required, (Schema) component, visitedSchemas); + if (schema.getAnyOf() != null) { + for (Object component : schema.getAnyOf()) { + addProperties(properties, required, (Schema) component, visitedSchemas); + } } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java index 8036919680b8..9db67fbb3982 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java @@ -145,6 +145,7 @@ public AbstractKotlinCodegen() { super(); supportsInheritance = true; + skipOneOfPropertyMergeInParent = true; setSortModelPropertiesByRequiredFlag(true); languageSpecificPrimitives = new HashSet<>(Arrays.asList( diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java index 95bfa644bd4f..47d14cf3c0d3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -1084,6 +1084,41 @@ public void paramJsonPropertyAnnotationWithDigitStartingPropertyName() throws IO "@param:JsonProperty(\"2nd_field\")\n @get:JsonProperty(\"2nd_field\")\n val `2ndField`"); } + @Test + public void testOneOfAllOfDiscriminatorInheritancePropertiesNotLeakedToParent() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("kotlin") + .addAdditionalProperty("serializationLibrary", "jackson") + .addAdditionalProperty("removeDiscriminatorFromChildModels", true) + .setInputSpec("src/test/resources/3_1/polymorphism-allof-and-oneof-discriminator.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(configurator.toClientOptInput()).generate(); + + Path petModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Pet.kt"); + // don't include child's fields into interface class + TestUtils.assertFileNotContains(petModel, "packSize"); + TestUtils.assertFileNotContains(petModel, "huntingSkill"); + TestUtils.assertFileContains(petModel, "name"); + + // cat contains only cat properties + parent + Path catModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Cat.kt"); + TestUtils.assertFileContains(catModel, "huntingSkill"); + TestUtils.assertFileNotContains(catModel, "packSize"); + TestUtils.assertFileContains(petModel, "name"); + + // dog contains only dog properties + parent + Path dogModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Dog.kt"); + TestUtils.assertFileNotContains(dogModel, "huntingSkill"); + TestUtils.assertFileContains(dogModel, "packSize"); + TestUtils.assertFileContains(petModel, "name"); + } + + private static class ModelNameTest { private final String expectedName; private final String expectedClassName; diff --git a/modules/openapi-generator/src/test/resources/3_1/polymorphism-allof-and-oneof-discriminator.yaml b/modules/openapi-generator/src/test/resources/3_1/polymorphism-allof-and-oneof-discriminator.yaml new file mode 100644 index 000000000000..f369c3c52ffb --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/polymorphism-allof-and-oneof-discriminator.yaml @@ -0,0 +1,69 @@ +# Test spec for allOf/oneOf inheritance with discriminator. +# Verifies that child properties (e.g. huntingSkill, packSize) are not leaked +# into the parent interface or sibling models. +# Based on OAS 3.1 example: https://spec.openapis.org/oas/v3.2.0.html#models-with-polymorphism-support-and-a-discriminator-object +openapi: 3.1.0 +info: + title: Polymorphism with allOf, oneOf and discriminator + version: "1.0" +paths: {} +components: + schemas: + Pet: + description: A pet + type: object + discriminator: + propertyName: petType + mapping: + cat: '#/components/schemas/Cat' + dog: '#/components/schemas/Dog' + properties: + name: + type: string + required: + - name + - petType + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + Cat: + description: A pet cat + type: object + allOf: + - $ref: '#/components/schemas/Pet' + properties: + petType: + const: 'cat' + huntingSkill: + type: string + description: The measured skill for hunting + enum: + - clueless + - lazy + - adventurous + - aggressive + required: + - huntingSkill + Dog: + description: A pet dog + type: object + allOf: + - $ref: '#/components/schemas/Pet' + properties: + petType: + const: 'dog' + packSize: + type: integer + format: int32 + description: the size of the pack the dog is from + default: 0 + minimum: 0 + required: + - petType + - packSize + House: + description: A house + type: object + properties: + pet: + $ref: '#/components/schemas/Pet' From 380b22516ce9885cb9e14d4e281909480d0be817 Mon Sep 17 00:00:00 2001 From: Denis Belyanski Date: Tue, 30 Jun 2026 13:23:26 +0300 Subject: [PATCH 2/5] [Kotlin-client] decouple skipOneOfPropertyMergeInParent from supportsInheritance - Remove supportsInheritance gate from the non-merge branch condition to avoid silently re-enabling child-property merging for generators using alternative polymorphism strategies - Move skipOneOfPropertyMergeInParent flag from AbstractKotlinCodegen to KotlinClientCodegen (more specific scope) - Fix test assertions to check correct model paths --- .../main/java/org/openapitools/codegen/DefaultCodegen.java | 2 +- .../openapitools/codegen/languages/AbstractKotlinCodegen.java | 1 - .../openapitools/codegen/languages/KotlinClientCodegen.java | 2 ++ .../codegen/kotlin/KotlinClientCodegenModelTest.java | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) 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 eff3f8feea4f..c5faaa382d8e 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 @@ -2847,7 +2847,7 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map()); - } else if (skipOneOfPropertyMergeInParent && composed.getOneOf() != null && composed.getDiscriminator() != null && supportsInheritance) { + } else if (skipOneOfPropertyMergeInParent && composed.getOneOf() != null && composed.getDiscriminator() != null) { // polymorphic parent with discriminator and oneOf children — // these are type alternatives (subtypes), not compositions, // so their properties should not be merged into the parent diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java index 9db67fbb3982..8036919680b8 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java @@ -145,7 +145,6 @@ public AbstractKotlinCodegen() { super(); supportsInheritance = true; - skipOneOfPropertyMergeInParent = true; setSortModelPropertiesByRequiredFlag(true); languageSpecificPrimitives = new HashSet<>(Arrays.asList( diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java index c8d99cab3f85..0ca61b00f90f 100755 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java @@ -307,6 +307,8 @@ public KotlinClientCodegen() { cliOptions.add(CliOption.newBoolean(USE_JACKSON_3, "Use Jackson 3 dependencies (tools.jackson package). Not yet supported for kotlin-client; reserved for future use.")); + + skipOneOfPropertyMergeInParent = true; } @Override diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java index 47d14cf3c0d3..aa9639cdbaf9 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -1109,13 +1109,13 @@ public void testOneOfAllOfDiscriminatorInheritancePropertiesNotLeakedToParent() Path catModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Cat.kt"); TestUtils.assertFileContains(catModel, "huntingSkill"); TestUtils.assertFileNotContains(catModel, "packSize"); - TestUtils.assertFileContains(petModel, "name"); + TestUtils.assertFileContains(catModel, "name"); // dog contains only dog properties + parent Path dogModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Dog.kt"); TestUtils.assertFileNotContains(dogModel, "huntingSkill"); TestUtils.assertFileContains(dogModel, "packSize"); - TestUtils.assertFileContains(petModel, "name"); + TestUtils.assertFileContains(dogModel, "name"); } From 646e880213332de90067165387ca1231c7d10319 Mon Sep 17 00:00:00 2001 From: Denis Belyanski Date: Tue, 30 Jun 2026 15:14:39 +0300 Subject: [PATCH 3/5] Add comprehensive polymorphism tests for all serialization libraries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameterize existing polymorphism tests to run against all 4 Kotlin serialization libraries (jackson, moshi, gson, kotlinx_serialization), covering: - Plain oneOf without discriminator (wrapper model) - oneOf with discriminator (no child property leaking) - Plain allOf without discriminator (inheritance preserved) - allOf with discriminator (no child property leaking) Simplify DefaultCodegen guards: remove discriminator-scoping from addProperties() since the flag is set at the KotlinClientCodegen level for all serialization libraries. Move flag from Jackson-only processOpts back to constructor — the bug affects all serializers equally and tests confirm the fix is safe across all configurations. --- .../kotlin/KotlinClientCodegenModelTest.java | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java index aa9639cdbaf9..7002b9e03ab9 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -1133,4 +1133,148 @@ private ModelNameTest(String expectedName, String expectedClassName) { this.expectedClassName = expectedClassName; } } + + @Test(dataProvider = "serializationLibraries") + public void testPlainOneOfWithoutDiscriminatorGeneratesWrapperModel(String serializationLibrary) throws Exception { + // Plain oneOf without discriminator generates a wrapper data class containing all variants' properties. + // This is intentional — without discriminator there's no polymorphism, just a union type. + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("kotlin") + .addAdditionalProperty("serializationLibrary", serializationLibrary) + .setInputSpec("src/test/resources/3_0/oneOf.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + Path fruitModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Fruit.kt"); + // Fruit is a data class (wrapper) that includes its own prop + all children's props + TestUtils.assertFileContains(fruitModel, "color"); + TestUtils.assertFileContains(fruitModel, "kind"); // Apple's property — merged intentionally + TestUtils.assertFileContains(fruitModel, "count"); // Banana's property — merged intentionally + TestUtils.assertFileContains(fruitModel, "sweet"); // Orange's property — merged intentionally + + // Children are standalone models with only their own properties + Path appleModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Apple.kt"); + TestUtils.assertFileContains(appleModel, "kind"); + TestUtils.assertFileNotContains(appleModel, "count"); + TestUtils.assertFileNotContains(appleModel, "sweet"); + + Path bananaModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Banana.kt"); + TestUtils.assertFileContains(bananaModel, "count"); + TestUtils.assertFileNotContains(bananaModel, "kind"); + TestUtils.assertFileNotContains(bananaModel, "sweet"); + + Path orangeModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Orange.kt"); + TestUtils.assertFileContains(orangeModel, "sweet"); + TestUtils.assertFileNotContains(orangeModel, "kind"); + TestUtils.assertFileNotContains(orangeModel, "count"); + } + + @Test(dataProvider = "serializationLibraries") + public void testOneOfWithDiscriminatorPropertiesNotLeakedToParent(String serializationLibrary) throws Exception { + // oneOf with discriminator: FruitReqDisc has discriminator "fruitType", children have their own props + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("kotlin") + .addAdditionalProperty("serializationLibrary", serializationLibrary) + .setInputSpec("src/test/resources/3_0/oneOfDiscriminator.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + Path parentModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/FruitReqDisc.kt"); + // Parent should NOT have child-specific properties + TestUtils.assertFileNotContains(parentModel, "seeds"); // AppleReqDisc's property + TestUtils.assertFileNotContains(parentModel, "length"); // BananaReqDisc's property + + // Children should have only their own properties + discriminator + Path appleModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/AppleReqDisc.kt"); + TestUtils.assertFileContains(appleModel, "seeds"); + TestUtils.assertFileNotContains(appleModel, "length"); + + Path bananaModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/BananaReqDisc.kt"); + TestUtils.assertFileContains(bananaModel, "length"); + TestUtils.assertFileNotContains(bananaModel, "seeds"); + } + + @Test(dataProvider = "serializationLibraries") + public void testPlainAllOfWithoutDiscriminatorInheritancePreserved(String serializationLibrary) throws Exception { + // Plain allOf without discriminator: children inherit parent's properties via override + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("kotlin") + .addAdditionalProperty("serializationLibrary", serializationLibrary) + .setInputSpec("src/test/resources/3_0/allOf_extension_parent.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + Path personModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Person.kt"); + // Person should have its own properties + TestUtils.assertFileContains(personModel, "lastName"); + TestUtils.assertFileContains(personModel, "firstName"); + + // Adult should have its own property + parent properties via inheritance + Path adultModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Adult.kt"); + TestUtils.assertFileContains(adultModel, "children"); + TestUtils.assertFileContains(adultModel, "lastName"); + TestUtils.assertFileContains(adultModel, "firstName"); + + // Child should have its own property + parent properties via inheritance + Path childModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Child.kt"); + TestUtils.assertFileContains(childModel, "age"); + TestUtils.assertFileContains(childModel, "lastName"); + TestUtils.assertFileContains(childModel, "firstName"); + } + + @Test(dataProvider = "serializationLibraries") + public void testAllOfWithDiscriminatorPropertiesNotLeakedToParent(String serializationLibrary) throws Exception { + // allOf with discriminator: Pet is parent, Cat/Dog extend it + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("kotlin") + .addAdditionalProperty("serializationLibrary", serializationLibrary) + .setInputSpec("src/test/resources/3_1/polymorphism-allof-and-discriminator.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + + Path petModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Pet.kt"); + // Pet should have only its own property + TestUtils.assertFileContains(petModel, "name"); + TestUtils.assertFileNotContains(petModel, "packSize"); // Dog's property + TestUtils.assertFileNotContains(petModel, "huntingSkill"); // Cat's property + + // Cat should have its own + parent properties, NOT Dog's + Path catModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Cat.kt"); + TestUtils.assertFileContains(catModel, "huntingSkill"); + TestUtils.assertFileContains(catModel, "name"); + TestUtils.assertFileNotContains(catModel, "packSize"); + + // Dog should have its own + parent properties, NOT Cat's + Path dogModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Dog.kt"); + TestUtils.assertFileContains(dogModel, "packSize"); + TestUtils.assertFileContains(dogModel, "name"); + TestUtils.assertFileNotContains(dogModel, "huntingSkill"); + } + + @DataProvider(name = "serializationLibraries") + public Object[][] serializationLibraries() { + return new Object[][]{ + {"jackson"}, + {"moshi"}, + {"gson"}, + {"kotlinx_serialization"}, + }; + } + } From 081046dc09b78c2c75447fe7c4166cb69b2fe604 Mon Sep 17 00:00:00 2001 From: Denis Belyanski Date: Sat, 11 Jul 2026 11:46:12 +0300 Subject: [PATCH 4/5] update the samples --- .../kotlin-jvm-okhttp/docs/FormApi.md | 19 ++------- .../org/openapitools/client/apis/FormApi.kt | 41 ++++--------------- .../docs/FormApi.md | 19 ++------- .../org/openapitools/client/apis/FormApi.kt | 23 ++++------- .../docs/FormApi.md | 19 ++------- .../org/openapitools/client/apis/FormApi.kt | 23 ++++------- .../docs/FormApi.md | 17 +------- .../org/openapitools/client/apis/FormApi.kt | 9 +--- .../docs/Animal.md | 5 --- .../docs/AnotherAnimal.md | 5 --- .../builds/kebab-case/models/enum-test.ts | 2 +- .../builds/kebab-case/models/format-test.ts | 2 +- 12 files changed, 41 insertions(+), 143 deletions(-) diff --git a/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md b/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md index 5d229c127758..306c008f5d02 100644 --- a/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md @@ -60,7 +60,7 @@ No authorization required # **testFormOneof** -> kotlin.String testFormOneof(form1, form2, form3, form4, id, name) +> kotlin.String testFormOneof() Test form parameter(s) for oneOf schema @@ -73,14 +73,8 @@ Test form parameter(s) for oneOf schema //import org.openapitools.client.models.* val apiInstance = FormApi() -val form1 : kotlin.String = form1_example // kotlin.String | -val form2 : kotlin.Int = 56 // kotlin.Int | -val form3 : kotlin.String = form3_example // kotlin.String | -val form4 : kotlin.Boolean = true // kotlin.Boolean | -val id : kotlin.Long = 789 // kotlin.Long | -val name : kotlin.String = name_example // kotlin.String | try { - val result : kotlin.String = apiInstance.testFormOneof(form1, form2, form3, form4, id, name) + val result : kotlin.String = apiInstance.testFormOneof() println(result) } catch (e: ClientException) { println("4xx response calling FormApi#testFormOneof") @@ -92,14 +86,7 @@ try { ``` ### Parameters -| **form1** | **kotlin.String**| | [optional] | -| **form2** | **kotlin.Int**| | [optional] | -| **form3** | **kotlin.String**| | [optional] | -| **form4** | **kotlin.Boolean**| | [optional] | -| **id** | **kotlin.Long**| | [optional] | -| Name | Type | Description | Notes | -| ------------- | ------------- | ------------- | ------------- | -| **name** | **kotlin.String**| | [optional] | +This endpoint does not need any parameter. ### Return type diff --git a/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 0c4686509eb8..5250de9efb52 100644 --- a/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -138,12 +138,6 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto * POST /form/oneof * Test form parameter(s) for oneOf schema * Test form parameter(s) for oneOf schema - * @param form1 (optional) - * @param form2 (optional) - * @param form3 (optional) - * @param form4 (optional) - * @param id (optional) - * @param name (optional) * @return kotlin.String * @throws IllegalStateException If the request is not correctly configured * @throws IOException Rethrows the OkHttp execute method exception @@ -153,8 +147,8 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto */ @Suppress("UNCHECKED_CAST") @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) - fun testFormOneof(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null) : kotlin.String { - val localVarResponse = testFormOneofWithHttpInfo(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) + fun testFormOneof() : kotlin.String { + val localVarResponse = testFormOneofWithHttpInfo() return when (localVarResponse.responseType) { ResponseType.Success -> (localVarResponse as Success<*>).data as kotlin.String @@ -175,22 +169,16 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto * POST /form/oneof * Test form parameter(s) for oneOf schema * Test form parameter(s) for oneOf schema - * @param form1 (optional) - * @param form2 (optional) - * @param form3 (optional) - * @param form4 (optional) - * @param id (optional) - * @param name (optional) * @return ApiResponse * @throws IllegalStateException If the request is not correctly configured * @throws IOException Rethrows the OkHttp execute method exception */ @Suppress("UNCHECKED_CAST") @Throws(IllegalStateException::class, IOException::class) - fun testFormOneofWithHttpInfo(form1: kotlin.String?, form2: kotlin.Int?, form3: kotlin.String?, form4: kotlin.Boolean?, id: kotlin.Long?, name: kotlin.String?) : ApiResponse { - val localVariableConfig = testFormOneofRequestConfig(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) + fun testFormOneofWithHttpInfo() : ApiResponse { + val localVariableConfig = testFormOneofRequestConfig() - return request>, kotlin.String>( + return request( localVariableConfig ) } @@ -198,24 +186,13 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto /** * To obtain the request config of the operation testFormOneof * - * @param form1 (optional) - * @param form2 (optional) - * @param form3 (optional) - * @param form4 (optional) - * @param id (optional) - * @param name (optional) * @return RequestConfig */ - fun testFormOneofRequestConfig(form1: kotlin.String?, form2: kotlin.Int?, form3: kotlin.String?, form4: kotlin.Boolean?, id: kotlin.Long?, name: kotlin.String?) : RequestConfig>> { - val localVariableBody = mapOf( - "form1" to PartConfig(body = form1, headers = mutableMapOf()), - "form2" to PartConfig(body = form2, headers = mutableMapOf()), - "form3" to PartConfig(body = form3, headers = mutableMapOf()), - "form4" to PartConfig(body = form4, headers = mutableMapOf()), - "id" to PartConfig(body = id, headers = mutableMapOf()), - "name" to PartConfig(body = name, headers = mutableMapOf()),) + fun testFormOneofRequestConfig() : RequestConfig { + val localVariableBody = null val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/x-www-form-urlencoded" localVariableHeaders["Accept"] = "text/plain" return RequestConfig( diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md index 5d229c127758..306c008f5d02 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md @@ -60,7 +60,7 @@ No authorization required # **testFormOneof** -> kotlin.String testFormOneof(form1, form2, form3, form4, id, name) +> kotlin.String testFormOneof() Test form parameter(s) for oneOf schema @@ -73,14 +73,8 @@ Test form parameter(s) for oneOf schema //import org.openapitools.client.models.* val apiInstance = FormApi() -val form1 : kotlin.String = form1_example // kotlin.String | -val form2 : kotlin.Int = 56 // kotlin.Int | -val form3 : kotlin.String = form3_example // kotlin.String | -val form4 : kotlin.Boolean = true // kotlin.Boolean | -val id : kotlin.Long = 789 // kotlin.Long | -val name : kotlin.String = name_example // kotlin.String | try { - val result : kotlin.String = apiInstance.testFormOneof(form1, form2, form3, form4, id, name) + val result : kotlin.String = apiInstance.testFormOneof() println(result) } catch (e: ClientException) { println("4xx response calling FormApi#testFormOneof") @@ -92,14 +86,7 @@ try { ``` ### Parameters -| **form1** | **kotlin.String**| | [optional] | -| **form2** | **kotlin.Int**| | [optional] | -| **form3** | **kotlin.String**| | [optional] | -| **form4** | **kotlin.Boolean**| | [optional] | -| **id** | **kotlin.Long**| | [optional] | -| Name | Type | Description | Notes | -| ------------- | ------------- | ------------- | ------------- | -| **name** | **kotlin.String**| | [optional] | +This endpoint does not need any parameter. ### Return type diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 8ad7abcf78af..25e41017939e 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -83,29 +83,24 @@ open class FormApi(client: RestClient) : ApiClient(client) { @Throws(RestClientResponseException::class) - fun testFormOneof(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): kotlin.String { - val result = testFormOneofWithHttpInfo(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) + fun testFormOneof(): kotlin.String { + val result = testFormOneofWithHttpInfo() return result.body!! } @Throws(RestClientResponseException::class) - fun testFormOneofWithHttpInfo(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): ResponseEntity { - val localVariableConfig = testFormOneofRequestConfig(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) - return request>, kotlin.String>( + fun testFormOneofWithHttpInfo(): ResponseEntity { + val localVariableConfig = testFormOneofRequestConfig() + return request( localVariableConfig ) } - fun testFormOneofRequestConfig(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null) : RequestConfig>> { - val localVariableBody = mapOf( - "form1" to PartConfig(body = form1, headers = mutableMapOf()), - "form2" to PartConfig(body = form2, headers = mutableMapOf()), - "form3" to PartConfig(body = form3, headers = mutableMapOf()), - "form4" to PartConfig(body = form4, headers = mutableMapOf()), - "id" to PartConfig(body = id, headers = mutableMapOf()), - "name" to PartConfig(body = name, headers = mutableMapOf()),) + fun testFormOneofRequestConfig() : RequestConfig { + val localVariableBody = null val localVariableQuery = mutableMapOf>() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/x-www-form-urlencoded" localVariableHeaders["Accept"] = "text/plain" val params = mutableMapOf( diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md index 5d229c127758..306c008f5d02 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md @@ -60,7 +60,7 @@ No authorization required # **testFormOneof** -> kotlin.String testFormOneof(form1, form2, form3, form4, id, name) +> kotlin.String testFormOneof() Test form parameter(s) for oneOf schema @@ -73,14 +73,8 @@ Test form parameter(s) for oneOf schema //import org.openapitools.client.models.* val apiInstance = FormApi() -val form1 : kotlin.String = form1_example // kotlin.String | -val form2 : kotlin.Int = 56 // kotlin.Int | -val form3 : kotlin.String = form3_example // kotlin.String | -val form4 : kotlin.Boolean = true // kotlin.Boolean | -val id : kotlin.Long = 789 // kotlin.Long | -val name : kotlin.String = name_example // kotlin.String | try { - val result : kotlin.String = apiInstance.testFormOneof(form1, form2, form3, form4, id, name) + val result : kotlin.String = apiInstance.testFormOneof() println(result) } catch (e: ClientException) { println("4xx response calling FormApi#testFormOneof") @@ -92,14 +86,7 @@ try { ``` ### Parameters -| **form1** | **kotlin.String**| | [optional] | -| **form2** | **kotlin.Int**| | [optional] | -| **form3** | **kotlin.String**| | [optional] | -| **form4** | **kotlin.Boolean**| | [optional] | -| **id** | **kotlin.Long**| | [optional] | -| Name | Type | Description | Notes | -| ------------- | ------------- | ------------- | ------------- | -| **name** | **kotlin.String**| | [optional] | +This endpoint does not need any parameter. ### Return type diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 80d01edfbb1e..e62236f0c6a1 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -87,29 +87,24 @@ open class FormApi(client: WebClient) : ApiClient(client) { @Throws(WebClientResponseException::class) - fun testFormOneof(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): Mono { - return testFormOneofWithHttpInfo(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) + fun testFormOneof(): Mono { + return testFormOneofWithHttpInfo() .map { it.body!! } } @Throws(WebClientResponseException::class) - fun testFormOneofWithHttpInfo(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): Mono> { - val localVariableConfig = testFormOneofRequestConfig(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) - return request>, kotlin.String>( + fun testFormOneofWithHttpInfo(): Mono> { + val localVariableConfig = testFormOneofRequestConfig() + return request( localVariableConfig ) } - fun testFormOneofRequestConfig(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null) : RequestConfig>> { - val localVariableBody = mapOf( - "form1" to PartConfig(body = form1, headers = mutableMapOf()), - "form2" to PartConfig(body = form2, headers = mutableMapOf()), - "form3" to PartConfig(body = form3, headers = mutableMapOf()), - "form4" to PartConfig(body = form4, headers = mutableMapOf()), - "id" to PartConfig(body = id, headers = mutableMapOf()), - "name" to PartConfig(body = name, headers = mutableMapOf()),) + fun testFormOneofRequestConfig() : RequestConfig { + val localVariableBody = null val localVariableQuery = mutableMapOf>() - val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") + val localVariableHeaders: MutableMap = mutableMapOf() + localVariableHeaders["Content-Type"] = "application/x-www-form-urlencoded" localVariableHeaders["Accept"] = "text/plain" val params = mutableMapOf( diff --git a/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md b/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md index fbc8495e9d05..eda2cbbcc751 100644 --- a/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md @@ -65,27 +65,14 @@ Test form parameter(s) for oneOf schema val apiClient = ApiClient() val webService = apiClient.createWebservice(FormApi::class.java) -val form1 : kotlin.String = form1_example // kotlin.String | -val form2 : kotlin.Int = 56 // kotlin.Int | -val form3 : kotlin.String = form3_example // kotlin.String | -val form4 : kotlin.Boolean = true // kotlin.Boolean | -val id : kotlin.Long = 789 // kotlin.Long | -val name : kotlin.String = name_example // kotlin.String | launch(Dispatchers.IO) { - val result : kotlin.String = webService.testFormOneof(form1, form2, form3, form4, id, name) + val result : kotlin.String = webService.testFormOneof() } ``` ### Parameters -| **form1** | **kotlin.String**| | [optional] | -| **form2** | **kotlin.Int**| | [optional] | -| **form3** | **kotlin.String**| | [optional] | -| **form4** | **kotlin.Boolean**| | [optional] | -| **id** | **kotlin.Long**| | [optional] | -| Name | Type | Description | Notes | -| ------------- | ------------- | ------------- | ------------- | -| **name** | **kotlin.String**| | [optional] | +This endpoint does not need any parameter. ### Return type diff --git a/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 2934b1b93e5a..6924af4efb04 100644 --- a/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -31,16 +31,9 @@ interface FormApi { * Responses: * - 200: Successful operation * - * @param form1 (optional) - * @param form2 (optional) - * @param form3 (optional) - * @param form4 (optional) - * @param id (optional) - * @param name (optional) * @return [kotlin.String] */ - @FormUrlEncoded @POST("form/oneof") - suspend fun testFormOneof(@Field("form1") form1: kotlin.String? = null, @Field("form2") form2: kotlin.Int? = null, @Field("form3") form3: kotlin.String? = null, @Field("form4") form4: kotlin.Boolean? = null, @Field("id") id: kotlin.Long? = null, @Field("name") name: kotlin.String? = null): Response + suspend fun testFormOneof(): Response } diff --git a/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/Animal.md b/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/Animal.md index aaffda15d819..f7f8dfd7e74e 100644 --- a/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/Animal.md +++ b/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/Animal.md @@ -4,11 +4,6 @@ ## Properties | Name | Type | Description | Notes | | ------------ | ------------- | ------------- | ------------- | -| **discriminator** | **kotlin.String** | | | -| **anotherDiscriminator** | **kotlin.String** | | | -| **propertyA** | **kotlin.String** | | [optional] | -| **sameNameProperty** | **kotlin.String** | | [optional] | -| **propertyB** | **kotlin.String** | | [optional] | diff --git a/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/AnotherAnimal.md b/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/AnotherAnimal.md index c5bc8ab96395..86b54cd3638d 100644 --- a/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/AnotherAnimal.md +++ b/samples/client/others/kotlin-oneOf-discriminator-kotlinx-serialization/docs/AnotherAnimal.md @@ -4,11 +4,6 @@ ## Properties | Name | Type | Description | Notes | | ------------ | ------------- | ------------- | ------------- | -| **discriminator** | **kotlin.String** | | | -| **anotherDiscriminator** | **kotlin.String** | | | -| **propertyA** | **kotlin.String** | | [optional] | -| **sameNameProperty** | **kotlin.String** | | [optional] | -| **propertyB** | **kotlin.String** | | [optional] | diff --git a/samples/client/petstore/typescript-fetch/builds/kebab-case/models/enum-test.ts b/samples/client/petstore/typescript-fetch/builds/kebab-case/models/enum-test.ts index d23b075ea7d2..4ea9062e1ed8 100644 --- a/samples/client/petstore/typescript-fetch/builds/kebab-case/models/enum-test.ts +++ b/samples/client/petstore/typescript-fetch/builds/kebab-case/models/enum-test.ts @@ -142,7 +142,7 @@ export type EnumTestEnumNumberEnum = typeof EnumTestEnumNumberEnum[keyof typeof * Check if a given object implements the EnumTest interface. */ export function instanceOfEnumTest(value: object): value is EnumTest { - if ((!('enumStringRequired' in value) && !('enum_string_required' in value)) || (value['enumStringRequired'] === undefined && value['enum_string_required'] === undefined)) return false; + if ((!('enumStringRequired' in (value as Record)) && !('enum_string_required' in (value as Record))) || ((value as Record)['enumStringRequired'] === undefined && (value as Record)['enum_string_required'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/kebab-case/models/format-test.ts b/samples/client/petstore/typescript-fetch/builds/kebab-case/models/format-test.ts index 822bb60636fb..c80896e71861 100644 --- a/samples/client/petstore/typescript-fetch/builds/kebab-case/models/format-test.ts +++ b/samples/client/petstore/typescript-fetch/builds/kebab-case/models/format-test.ts @@ -122,7 +122,7 @@ export interface FormatTest { */ export function instanceOfFormatTest(value: object): value is FormatTest { if (!('number' in value) || value['number'] === undefined) return false; - if ((!('_byte' in value) && !('byte' in value)) || (value['_byte'] === undefined && value['byte'] === undefined)) return false; + if ((!('_byte' in (value as Record)) && !('byte' in (value as Record))) || ((value as Record)['_byte'] === undefined && (value as Record)['byte'] === undefined)) return false; if (!('date' in value) || value['date'] === undefined) return false; if (!('password' in value) || value['password'] === undefined) return false; return true; From 84e881f0b6d7ab1f436c0b9288b055bd9c4680df Mon Sep 17 00:00:00 2001 From: Denis Belyanski Date: Sat, 11 Jul 2026 12:37:35 +0300 Subject: [PATCH 5/5] [Kotlin-client] fix form-data model generation for generation with skipOneOfPropertyMergeInParent. Update samples and docs --- .../openapitools/codegen/DefaultCodegen.java | 38 +++++++++++++---- .../languages/ProtobufSchemaCodegen.java | 4 +- .../kotlin-jvm-okhttp/docs/FormApi.md | 19 +++++++-- .../org/openapitools/client/apis/FormApi.kt | 41 +++++++++++++++---- .../docs/FormApi.md | 19 +++++++-- .../org/openapitools/client/apis/FormApi.kt | 23 +++++++---- .../docs/FormApi.md | 19 +++++++-- .../org/openapitools/client/apis/FormApi.kt | 23 +++++++---- .../docs/FormApi.md | 17 +++++++- .../org/openapitools/client/apis/FormApi.kt | 9 +++- 10 files changed, 163 insertions(+), 49 deletions(-) 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 c5faaa382d8e..8e688ecff4c3 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 @@ -3720,7 +3720,10 @@ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Sc } /** - * Add schema's properties to "properties" and "required" list + * Add schema's properties to "properties" and "required" list. + * + *

oneOf/anyOf sub-schema properties are merged into the parent only when + * {@code skipOneOfPropertyMergeInParent} is disabled (model generation default). * * @param properties all properties * @param required required property only @@ -3728,6 +3731,22 @@ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Sc * @param visitedSchemas circuit-breaker - the schemas with which the method was called before for recursive structures */ protected void addProperties(Map properties, List required, Schema schema, Set visitedSchemas) { + addProperties(properties, required, schema, visitedSchemas, false); + } + + /** + * Add schema's properties to "properties" and "required" list. + * + * @param properties all properties + * @param required required property only + * @param schema schema in which the properties will be added to the lists + * @param visitedSchemas circuit-breaker - the schemas with which the method was called before for recursive structures + * @param flattenComposedSchemas when true, oneOf/anyOf sub-schema properties are always flattened into the + * parent regardless of {@code skipOneOfPropertyMergeInParent}. This is required + * for form-data parameters, which need an individual field per alternative, + * unlike model definitions where alternatives are represented as separate types. + */ + protected void addProperties(Map properties, List required, Schema schema, Set visitedSchemas, boolean flattenComposedSchemas) { if (schema == null) { return; } @@ -3743,7 +3762,7 @@ protected void addProperties(Map properties, List requir if (schema.getAllOf() != null) { for (Object component : schema.getAllOf()) { - addProperties(properties, required, (Schema) component, visitedSchemas); + addProperties(properties, required, (Schema) component, visitedSchemas, flattenComposedSchemas); } } @@ -3753,17 +3772,18 @@ protected void addProperties(Map properties, List requir // Note: oneOf and anyOf represent type alternatives, not compositions. // When skipOneOfPropertyMergeInParent is enabled, their children's properties - // should NOT be merged into the parent schema. - if (!skipOneOfPropertyMergeInParent) { + // should NOT be merged into the parent schema - unless flattenComposedSchemas is + // requested (e.g. for form-data parameters, which need one field per alternative). + if (flattenComposedSchemas || !skipOneOfPropertyMergeInParent) { if (schema.getOneOf() != null) { for (Object component : schema.getOneOf()) { - addProperties(properties, required, (Schema) component, visitedSchemas); + addProperties(properties, required, (Schema) component, visitedSchemas, flattenComposedSchemas); } } if (schema.getAnyOf() != null) { for (Object component : schema.getAnyOf()) { - addProperties(properties, required, (Schema) component, visitedSchemas); + addProperties(properties, required, (Schema) component, visitedSchemas, flattenComposedSchemas); } } } @@ -3773,7 +3793,7 @@ protected void addProperties(Map properties, List requir if (StringUtils.isNotBlank(schema.get$ref())) { Schema interfaceSchema = ModelUtils.getReferencedSchema(this.openAPI, schema); - addProperties(properties, required, interfaceSchema, visitedSchemas); + addProperties(properties, required, interfaceSchema, visitedSchemas, flattenComposedSchemas); return; } if (schema.getProperties() != null) { @@ -7463,9 +7483,11 @@ public List fromRequestBodyToFormParameters(RequestBody body, // TODO in the future have this return one codegenParameter of type object or composed which includes all definition // that will be needed for complex composition use cases // https://github.com/OpenAPITools/openapi-generator/issues/10415 - addProperties(properties, allRequired, schema, new HashSet<>()); + // For form data, always flatten oneOf/anyOf alternatives into individual fields + // regardless of skipOneOfPropertyMergeInParent (which only applies to model generation). boolean isOneOfOrAnyOf = ModelUtils.isOneOf(schema) || ModelUtils.isAnyOf(schema); + addProperties(properties, allRequired, schema, new HashSet<>(), isOneOfOrAnyOf); if (!properties.isEmpty()) { for (Map.Entry entry : properties.entrySet()) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java index dac8e52cfcd5..f093394abe70 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java @@ -1955,8 +1955,8 @@ public GeneratorLanguage generatorLanguage() { * In this case, the second allOf that defines a map with string keys and Pet values will be part of model's property. */ @Override - protected void addProperties(Map properties, List required, Schema schema, Set visitedSchemas){ - super.addProperties(properties, required, schema, visitedSchemas); + protected void addProperties(Map properties, List required, Schema schema, Set visitedSchemas, boolean flattenComposedSchemas){ + super.addProperties(properties, required, schema, visitedSchemas, flattenComposedSchemas); if(schema.getAdditionalProperties() != null) { String addtionalPropertiesName = "default_map"; if(schema.getTitle() != null) { diff --git a/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md b/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md index 306c008f5d02..5d229c127758 100644 --- a/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-jvm-okhttp/docs/FormApi.md @@ -60,7 +60,7 @@ No authorization required # **testFormOneof** -> kotlin.String testFormOneof() +> kotlin.String testFormOneof(form1, form2, form3, form4, id, name) Test form parameter(s) for oneOf schema @@ -73,8 +73,14 @@ Test form parameter(s) for oneOf schema //import org.openapitools.client.models.* val apiInstance = FormApi() +val form1 : kotlin.String = form1_example // kotlin.String | +val form2 : kotlin.Int = 56 // kotlin.Int | +val form3 : kotlin.String = form3_example // kotlin.String | +val form4 : kotlin.Boolean = true // kotlin.Boolean | +val id : kotlin.Long = 789 // kotlin.Long | +val name : kotlin.String = name_example // kotlin.String | try { - val result : kotlin.String = apiInstance.testFormOneof() + val result : kotlin.String = apiInstance.testFormOneof(form1, form2, form3, form4, id, name) println(result) } catch (e: ClientException) { println("4xx response calling FormApi#testFormOneof") @@ -86,7 +92,14 @@ try { ``` ### Parameters -This endpoint does not need any parameter. +| **form1** | **kotlin.String**| | [optional] | +| **form2** | **kotlin.Int**| | [optional] | +| **form3** | **kotlin.String**| | [optional] | +| **form4** | **kotlin.Boolean**| | [optional] | +| **id** | **kotlin.Long**| | [optional] | +| Name | Type | Description | Notes | +| ------------- | ------------- | ------------- | ------------- | +| **name** | **kotlin.String**| | [optional] | ### Return type diff --git a/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 5250de9efb52..0c4686509eb8 100644 --- a/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -138,6 +138,12 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto * POST /form/oneof * Test form parameter(s) for oneOf schema * Test form parameter(s) for oneOf schema + * @param form1 (optional) + * @param form2 (optional) + * @param form3 (optional) + * @param form4 (optional) + * @param id (optional) + * @param name (optional) * @return kotlin.String * @throws IllegalStateException If the request is not correctly configured * @throws IOException Rethrows the OkHttp execute method exception @@ -147,8 +153,8 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto */ @Suppress("UNCHECKED_CAST") @Throws(IllegalStateException::class, IOException::class, UnsupportedOperationException::class, ClientException::class, ServerException::class) - fun testFormOneof() : kotlin.String { - val localVarResponse = testFormOneofWithHttpInfo() + fun testFormOneof(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null) : kotlin.String { + val localVarResponse = testFormOneofWithHttpInfo(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) return when (localVarResponse.responseType) { ResponseType.Success -> (localVarResponse as Success<*>).data as kotlin.String @@ -169,16 +175,22 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto * POST /form/oneof * Test form parameter(s) for oneOf schema * Test form parameter(s) for oneOf schema + * @param form1 (optional) + * @param form2 (optional) + * @param form3 (optional) + * @param form4 (optional) + * @param id (optional) + * @param name (optional) * @return ApiResponse * @throws IllegalStateException If the request is not correctly configured * @throws IOException Rethrows the OkHttp execute method exception */ @Suppress("UNCHECKED_CAST") @Throws(IllegalStateException::class, IOException::class) - fun testFormOneofWithHttpInfo() : ApiResponse { - val localVariableConfig = testFormOneofRequestConfig() + fun testFormOneofWithHttpInfo(form1: kotlin.String?, form2: kotlin.Int?, form3: kotlin.String?, form4: kotlin.Boolean?, id: kotlin.Long?, name: kotlin.String?) : ApiResponse { + val localVariableConfig = testFormOneofRequestConfig(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) - return request( + return request>, kotlin.String>( localVariableConfig ) } @@ -186,13 +198,24 @@ open class FormApi(basePath: kotlin.String = defaultBasePath, client: Call.Facto /** * To obtain the request config of the operation testFormOneof * + * @param form1 (optional) + * @param form2 (optional) + * @param form3 (optional) + * @param form4 (optional) + * @param id (optional) + * @param name (optional) * @return RequestConfig */ - fun testFormOneofRequestConfig() : RequestConfig { - val localVariableBody = null + fun testFormOneofRequestConfig(form1: kotlin.String?, form2: kotlin.Int?, form3: kotlin.String?, form4: kotlin.Boolean?, id: kotlin.Long?, name: kotlin.String?) : RequestConfig>> { + val localVariableBody = mapOf( + "form1" to PartConfig(body = form1, headers = mutableMapOf()), + "form2" to PartConfig(body = form2, headers = mutableMapOf()), + "form3" to PartConfig(body = form3, headers = mutableMapOf()), + "form4" to PartConfig(body = form4, headers = mutableMapOf()), + "id" to PartConfig(body = id, headers = mutableMapOf()), + "name" to PartConfig(body = name, headers = mutableMapOf()),) val localVariableQuery: MultiValueMap = mutableMapOf() - val localVariableHeaders: MutableMap = mutableMapOf() - localVariableHeaders["Content-Type"] = "application/x-www-form-urlencoded" + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") localVariableHeaders["Accept"] = "text/plain" return RequestConfig( diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md index 306c008f5d02..5d229c127758 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/docs/FormApi.md @@ -60,7 +60,7 @@ No authorization required # **testFormOneof** -> kotlin.String testFormOneof() +> kotlin.String testFormOneof(form1, form2, form3, form4, id, name) Test form parameter(s) for oneOf schema @@ -73,8 +73,14 @@ Test form parameter(s) for oneOf schema //import org.openapitools.client.models.* val apiInstance = FormApi() +val form1 : kotlin.String = form1_example // kotlin.String | +val form2 : kotlin.Int = 56 // kotlin.Int | +val form3 : kotlin.String = form3_example // kotlin.String | +val form4 : kotlin.Boolean = true // kotlin.Boolean | +val id : kotlin.Long = 789 // kotlin.Long | +val name : kotlin.String = name_example // kotlin.String | try { - val result : kotlin.String = apiInstance.testFormOneof() + val result : kotlin.String = apiInstance.testFormOneof(form1, form2, form3, form4, id, name) println(result) } catch (e: ClientException) { println("4xx response calling FormApi#testFormOneof") @@ -86,7 +92,14 @@ try { ``` ### Parameters -This endpoint does not need any parameter. +| **form1** | **kotlin.String**| | [optional] | +| **form2** | **kotlin.Int**| | [optional] | +| **form3** | **kotlin.String**| | [optional] | +| **form4** | **kotlin.Boolean**| | [optional] | +| **id** | **kotlin.Long**| | [optional] | +| Name | Type | Description | Notes | +| ------------- | ------------- | ------------- | ------------- | +| **name** | **kotlin.String**| | [optional] | ### Return type diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 25e41017939e..8ad7abcf78af 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -83,24 +83,29 @@ open class FormApi(client: RestClient) : ApiClient(client) { @Throws(RestClientResponseException::class) - fun testFormOneof(): kotlin.String { - val result = testFormOneofWithHttpInfo() + fun testFormOneof(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): kotlin.String { + val result = testFormOneofWithHttpInfo(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) return result.body!! } @Throws(RestClientResponseException::class) - fun testFormOneofWithHttpInfo(): ResponseEntity { - val localVariableConfig = testFormOneofRequestConfig() - return request( + fun testFormOneofWithHttpInfo(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): ResponseEntity { + val localVariableConfig = testFormOneofRequestConfig(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) + return request>, kotlin.String>( localVariableConfig ) } - fun testFormOneofRequestConfig() : RequestConfig { - val localVariableBody = null + fun testFormOneofRequestConfig(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null) : RequestConfig>> { + val localVariableBody = mapOf( + "form1" to PartConfig(body = form1, headers = mutableMapOf()), + "form2" to PartConfig(body = form2, headers = mutableMapOf()), + "form3" to PartConfig(body = form3, headers = mutableMapOf()), + "form4" to PartConfig(body = form4, headers = mutableMapOf()), + "id" to PartConfig(body = id, headers = mutableMapOf()), + "name" to PartConfig(body = name, headers = mutableMapOf()),) val localVariableQuery = mutableMapOf>() - val localVariableHeaders: MutableMap = mutableMapOf() - localVariableHeaders["Content-Type"] = "application/x-www-form-urlencoded" + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") localVariableHeaders["Accept"] = "text/plain" val params = mutableMapOf( diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md index 306c008f5d02..5d229c127758 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/docs/FormApi.md @@ -60,7 +60,7 @@ No authorization required # **testFormOneof** -> kotlin.String testFormOneof() +> kotlin.String testFormOneof(form1, form2, form3, form4, id, name) Test form parameter(s) for oneOf schema @@ -73,8 +73,14 @@ Test form parameter(s) for oneOf schema //import org.openapitools.client.models.* val apiInstance = FormApi() +val form1 : kotlin.String = form1_example // kotlin.String | +val form2 : kotlin.Int = 56 // kotlin.Int | +val form3 : kotlin.String = form3_example // kotlin.String | +val form4 : kotlin.Boolean = true // kotlin.Boolean | +val id : kotlin.Long = 789 // kotlin.Long | +val name : kotlin.String = name_example // kotlin.String | try { - val result : kotlin.String = apiInstance.testFormOneof() + val result : kotlin.String = apiInstance.testFormOneof(form1, form2, form3, form4, id, name) println(result) } catch (e: ClientException) { println("4xx response calling FormApi#testFormOneof") @@ -86,7 +92,14 @@ try { ``` ### Parameters -This endpoint does not need any parameter. +| **form1** | **kotlin.String**| | [optional] | +| **form2** | **kotlin.Int**| | [optional] | +| **form3** | **kotlin.String**| | [optional] | +| **form4** | **kotlin.Boolean**| | [optional] | +| **id** | **kotlin.Long**| | [optional] | +| Name | Type | Description | Notes | +| ------------- | ------------- | ------------- | ------------- | +| **name** | **kotlin.String**| | [optional] | ### Return type diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index e62236f0c6a1..80d01edfbb1e 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -87,24 +87,29 @@ open class FormApi(client: WebClient) : ApiClient(client) { @Throws(WebClientResponseException::class) - fun testFormOneof(): Mono { - return testFormOneofWithHttpInfo() + fun testFormOneof(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): Mono { + return testFormOneofWithHttpInfo(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) .map { it.body!! } } @Throws(WebClientResponseException::class) - fun testFormOneofWithHttpInfo(): Mono> { - val localVariableConfig = testFormOneofRequestConfig() - return request( + fun testFormOneofWithHttpInfo(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null): Mono> { + val localVariableConfig = testFormOneofRequestConfig(form1 = form1, form2 = form2, form3 = form3, form4 = form4, id = id, name = name) + return request>, kotlin.String>( localVariableConfig ) } - fun testFormOneofRequestConfig() : RequestConfig { - val localVariableBody = null + fun testFormOneofRequestConfig(form1: kotlin.String? = null, form2: kotlin.Int? = null, form3: kotlin.String? = null, form4: kotlin.Boolean? = null, id: kotlin.Long? = null, name: kotlin.String? = null) : RequestConfig>> { + val localVariableBody = mapOf( + "form1" to PartConfig(body = form1, headers = mutableMapOf()), + "form2" to PartConfig(body = form2, headers = mutableMapOf()), + "form3" to PartConfig(body = form3, headers = mutableMapOf()), + "form4" to PartConfig(body = form4, headers = mutableMapOf()), + "id" to PartConfig(body = id, headers = mutableMapOf()), + "name" to PartConfig(body = name, headers = mutableMapOf()),) val localVariableQuery = mutableMapOf>() - val localVariableHeaders: MutableMap = mutableMapOf() - localVariableHeaders["Content-Type"] = "application/x-www-form-urlencoded" + val localVariableHeaders: MutableMap = mutableMapOf("Content-Type" to "application/x-www-form-urlencoded") localVariableHeaders["Accept"] = "text/plain" val params = mutableMapOf( diff --git a/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md b/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md index eda2cbbcc751..fbc8495e9d05 100644 --- a/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md +++ b/samples/client/echo_api/kotlin-model-prefix-type-mappings/docs/FormApi.md @@ -65,14 +65,27 @@ Test form parameter(s) for oneOf schema val apiClient = ApiClient() val webService = apiClient.createWebservice(FormApi::class.java) +val form1 : kotlin.String = form1_example // kotlin.String | +val form2 : kotlin.Int = 56 // kotlin.Int | +val form3 : kotlin.String = form3_example // kotlin.String | +val form4 : kotlin.Boolean = true // kotlin.Boolean | +val id : kotlin.Long = 789 // kotlin.Long | +val name : kotlin.String = name_example // kotlin.String | launch(Dispatchers.IO) { - val result : kotlin.String = webService.testFormOneof() + val result : kotlin.String = webService.testFormOneof(form1, form2, form3, form4, id, name) } ``` ### Parameters -This endpoint does not need any parameter. +| **form1** | **kotlin.String**| | [optional] | +| **form2** | **kotlin.Int**| | [optional] | +| **form3** | **kotlin.String**| | [optional] | +| **form4** | **kotlin.Boolean**| | [optional] | +| **id** | **kotlin.Long**| | [optional] | +| Name | Type | Description | Notes | +| ------------- | ------------- | ------------- | ------------- | +| **name** | **kotlin.String**| | [optional] | ### Return type diff --git a/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt b/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt index 6924af4efb04..2934b1b93e5a 100644 --- a/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt +++ b/samples/client/echo_api/kotlin-model-prefix-type-mappings/src/main/kotlin/org/openapitools/client/apis/FormApi.kt @@ -31,9 +31,16 @@ interface FormApi { * Responses: * - 200: Successful operation * + * @param form1 (optional) + * @param form2 (optional) + * @param form3 (optional) + * @param form4 (optional) + * @param id (optional) + * @param name (optional) * @return [kotlin.String] */ + @FormUrlEncoded @POST("form/oneof") - suspend fun testFormOneof(): Response + suspend fun testFormOneof(@Field("form1") form1: kotlin.String? = null, @Field("form2") form2: kotlin.Int? = null, @Field("form3") form3: kotlin.String? = null, @Field("form4") form4: kotlin.Boolean? = null, @Field("id") id: kotlin.Long? = null, @Field("name") name: kotlin.String? = null): Response }