From e5bc4c9a81076be0371efa6be8a23a25a7347acb Mon Sep 17 00:00:00 2001 From: xil Date: Thu, 8 Jan 2026 05:49:55 +0000 Subject: [PATCH 1/2] Improve Protobuf Generator's oneOf Handling allOf Unwrapping with Complex Types --- .../languages/ProtobufSchemaCodegen.java | 14 ++++- .../protobuf/ProtobufSchemaCodegenTest.java | 59 +++++++++++++++++++ .../src/test/resources/3_1/oneOf.yaml | 42 +++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/test/resources/3_1/oneOf.yaml 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 9738d9207281..4793dae5f9f9 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 @@ -442,7 +442,19 @@ private void processNestedSchemas(Schema schema, Set visitedSchemas) { List oneOfs = schema.getOneOf(); List newOneOfs = new ArrayList<>(); for (Schema oneOf : oneOfs) { - Schema oneOfSchema = ModelUtils.getReferencedSchema(openAPI, oneOf); + Schema oneOfSchema = oneOf; + if (ModelUtils.isAllOf(oneOf) && oneOf.getAllOf() != null && oneOf.getAllOf().size() == 1) { + Object allOfObj = oneOf.getAllOf().get(0); + if (allOfObj instanceof Schema) { + Schema allOfItem = (Schema) allOfObj; + if (StringUtils.isNotEmpty(allOfItem.get$ref())) { + oneOfSchema = ModelUtils.getReferencedSchema(openAPI, allOfItem); + } + } + } else { + oneOfSchema = ModelUtils.getReferencedSchema(openAPI, oneOf); + } + if (ModelUtils.isArraySchema(oneOfSchema)) { Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas); innerSchema.setTitle(oneOf.getTitle()); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java index b22a45b50395..498872d9970d 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java @@ -18,8 +18,10 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.BooleanSchema; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; @@ -45,6 +47,7 @@ import java.util.List; import java.util.Map; + import static org.openapitools.codegen.TestUtils.createCodegenModelWrapper; import static org.openapitools.codegen.languages.ProtobufSchemaCodegen.USE_SIMPLIFIED_ENUM_NAMES; import static org.testng.Assert.assertEquals; @@ -142,6 +145,62 @@ public void testCodeGenWithPrimitiveAnyOf() throws IOException { output.deleteOnExit(); } + @Test + public void testCodeGenWithOneOfDiscriminator31() throws IOException { + System.setProperty("line.separator", "\n"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("protobuf-schema") + .setInputSpec("src/test/resources/3_1/oneOf.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "models/fruit.proto"); + + // Get the processed OpenAPI with wrapper schemas + OpenAPI openAPI = clientOptInput.getOpenAPI(); + ProtobufSchemaCodegen codegen = new ProtobufSchemaCodegen(); + codegen.setOpenAPI(openAPI); + codegen.processOpts(); + + Schema fruitSchema = openAPI.getComponents().getSchemas().get("fruit"); + Assert.assertNotNull(fruitSchema, "fruit schema should exist"); + + CodegenModel fruitModel = codegen.fromModel("fruit", fruitSchema); + codegen.postProcessModels(createCodegenModelWrapper(fruitModel)); + + Assert.assertNotNull(fruitModel.oneOf, "fruit model should have oneOf items"); + Assert.assertTrue(fruitModel.oneOf.size() >= 2, "fruit model should have at least 2 oneOf items"); + + Assert.assertNotNull(fruitModel.vars, "fruit model should have vars"); + Assert.assertTrue(fruitModel.vars.size() > 0, "fruit model should have at least one var"); + + Assert.assertEquals(fruitModel.vars.size(), 3, "fruit model should have 3 vars (one for each oneOf item)"); + + for (CodegenProperty var : fruitModel.vars) { + Assert.assertNotNull(var.name, "var name should not be null"); + Assert.assertNotNull(var.dataType, "var dataType should not be null"); + Assert.assertTrue(var.isModel, "var should be a model type"); + Assert.assertFalse(var.isContainer, "var should not be a container (it references a model)"); + + // Check expected properties based on discriminator title + if (var.name.equals("apple_list")) { + Assert.assertEquals(var.dataType, "StringArray", "apple_list should reference StringArray"); + } else if (var.name.equals("banana_map")) { + Assert.assertEquals(var.dataType, "FloatMap", "banana_map should reference FloatMap"); + } else if (var.name.equals("orange_choice")) { + Assert.assertEquals(var.dataType, "Orange", "orange_choice should reference Orange"); + } + } + + output.deleteOnExit(); + } + @Test(description = "convert a model with dollar signs") public void modelTest() { final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/dollar-in-names-pull14359.yaml"); diff --git a/modules/openapi-generator/src/test/resources/3_1/oneOf.yaml b/modules/openapi-generator/src/test/resources/3_1/oneOf.yaml new file mode 100644 index 000000000000..fe3549d551a8 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/oneOf.yaml @@ -0,0 +1,42 @@ +openapi: 3.1.0 + +info: + title: fruity + version: 0.0.1 + +paths: + /: + get: + responses: + '200': + description: desc + content: + application/json: + schema: + $ref: '#/components/schemas/fruit' + +components: + schemas: + fruit: + oneOf: + - title: appleList + $ref: '#/components/schemas/appleArray' + - title: bananaMap + $ref: '#/components/schemas/bananaMap' + - title: orangeChoice + $ref: '#/components/schemas/orange' + appleArray: + type: array + items: + title: appleaArray + type: string + bananaMap: + type: object + additionalProperties: + type: number + orange: + title: orange + type: object + properties: + sweet: + type: boolean From 74c65a21efc8e4c3e80119ecd0fe927acd5304c6 Mon Sep 17 00:00:00 2001 From: xil Date: Thu, 15 Jan 2026 21:20:30 +0000 Subject: [PATCH 2/2] Address cubic-dev-ai comment --- .../codegen/protobuf/ProtobufSchemaCodegenTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java index 498872d9970d..40d0f3f81b80 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java @@ -185,7 +185,7 @@ public void testCodeGenWithOneOfDiscriminator31() throws IOException { for (CodegenProperty var : fruitModel.vars) { Assert.assertNotNull(var.name, "var name should not be null"); Assert.assertNotNull(var.dataType, "var dataType should not be null"); - Assert.assertTrue(var.isModel, "var should be a model type"); + Assert.assertTrue(var.isModel, "var " + var.name + " should be a model type (isModel=" + var.isModel + ")"); Assert.assertFalse(var.isContainer, "var should not be a container (it references a model)"); // Check expected properties based on discriminator title @@ -195,6 +195,8 @@ public void testCodeGenWithOneOfDiscriminator31() throws IOException { Assert.assertEquals(var.dataType, "FloatMap", "banana_map should reference FloatMap"); } else if (var.name.equals("orange_choice")) { Assert.assertEquals(var.dataType, "Orange", "orange_choice should reference Orange"); + } else { + Assert.fail("Unexpected var name: " + var.name + ". Expected one of: apple_list, banana_map, orange_choice"); } }