[Protobuf] Improve oneOf Handling by Unwrapping allOf for Complex Types (#22700)

* Improve Protobuf Generator's oneOf Handling allOf Unwrapping with Complex Types

* Address cubic-dev-ai comment
This commit is contained in:
Xi Lu
2026-01-19 01:00:31 -08:00
committed by GitHub
parent 502565b317
commit 24242be595
3 changed files with 116 additions and 1 deletions

View File

@@ -442,7 +442,19 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
List<Schema> oneOfs = schema.getOneOf();
List<Schema> 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());

View File

@@ -18,8 +18,10 @@ package org.openapitools.codegen.protobuf;
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.Arrays;
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,64 @@ public class ProtobufSchemaCodegenTest {
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<File> 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 " + 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
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");
} else {
Assert.fail("Unexpected var name: " + var.name + ". Expected one of: apple_list, banana_map, orange_choice");
}
}
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");

View File

@@ -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