Skip to content

Commit 24242be

Browse files
authored
[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
1 parent 502565b commit 24242be

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,19 @@ private void processNestedSchemas(Schema schema, Set<Schema> visitedSchemas) {
442442
List<Schema> oneOfs = schema.getOneOf();
443443
List<Schema> newOneOfs = new ArrayList<>();
444444
for (Schema oneOf : oneOfs) {
445-
Schema oneOfSchema = ModelUtils.getReferencedSchema(openAPI, oneOf);
445+
Schema oneOfSchema = oneOf;
446+
if (ModelUtils.isAllOf(oneOf) && oneOf.getAllOf() != null && oneOf.getAllOf().size() == 1) {
447+
Object allOfObj = oneOf.getAllOf().get(0);
448+
if (allOfObj instanceof Schema) {
449+
Schema allOfItem = (Schema) allOfObj;
450+
if (StringUtils.isNotEmpty(allOfItem.get$ref())) {
451+
oneOfSchema = ModelUtils.getReferencedSchema(openAPI, allOfItem);
452+
}
453+
}
454+
} else {
455+
oneOfSchema = ModelUtils.getReferencedSchema(openAPI, oneOf);
456+
}
457+
446458
if (ModelUtils.isArraySchema(oneOfSchema)) {
447459
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
448460
innerSchema.setTitle(oneOf.getTitle());

modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import io.swagger.v3.oas.models.OpenAPI;
2020
import io.swagger.v3.oas.models.media.ArraySchema;
21+
import io.swagger.v3.oas.models.media.BooleanSchema;
2122
import io.swagger.v3.oas.models.media.IntegerSchema;
2223
import io.swagger.v3.oas.models.media.MapSchema;
24+
import io.swagger.v3.oas.models.media.NumberSchema;
2325
import io.swagger.v3.oas.models.media.ObjectSchema;
2426
import io.swagger.v3.oas.models.media.Schema;
2527
import io.swagger.v3.oas.models.media.StringSchema;
@@ -45,6 +47,7 @@
4547
import java.util.List;
4648
import java.util.Map;
4749

50+
4851
import static org.openapitools.codegen.TestUtils.createCodegenModelWrapper;
4952
import static org.openapitools.codegen.languages.ProtobufSchemaCodegen.USE_SIMPLIFIED_ENUM_NAMES;
5053
import static org.testng.Assert.assertEquals;
@@ -142,6 +145,64 @@ public void testCodeGenWithPrimitiveAnyOf() throws IOException {
142145
output.deleteOnExit();
143146
}
144147

148+
@Test
149+
public void testCodeGenWithOneOfDiscriminator31() throws IOException {
150+
System.setProperty("line.separator", "\n");
151+
152+
File output = Files.createTempDirectory("test").toFile();
153+
154+
final CodegenConfigurator configurator = new CodegenConfigurator()
155+
.setGeneratorName("protobuf-schema")
156+
.setInputSpec("src/test/resources/3_1/oneOf.yaml")
157+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
158+
159+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
160+
DefaultGenerator generator = new DefaultGenerator();
161+
List<File> files = generator.opts(clientOptInput).generate();
162+
163+
TestUtils.ensureContainsFile(files, output, "models/fruit.proto");
164+
165+
// Get the processed OpenAPI with wrapper schemas
166+
OpenAPI openAPI = clientOptInput.getOpenAPI();
167+
ProtobufSchemaCodegen codegen = new ProtobufSchemaCodegen();
168+
codegen.setOpenAPI(openAPI);
169+
codegen.processOpts();
170+
171+
Schema fruitSchema = openAPI.getComponents().getSchemas().get("fruit");
172+
Assert.assertNotNull(fruitSchema, "fruit schema should exist");
173+
174+
CodegenModel fruitModel = codegen.fromModel("fruit", fruitSchema);
175+
codegen.postProcessModels(createCodegenModelWrapper(fruitModel));
176+
177+
Assert.assertNotNull(fruitModel.oneOf, "fruit model should have oneOf items");
178+
Assert.assertTrue(fruitModel.oneOf.size() >= 2, "fruit model should have at least 2 oneOf items");
179+
180+
Assert.assertNotNull(fruitModel.vars, "fruit model should have vars");
181+
Assert.assertTrue(fruitModel.vars.size() > 0, "fruit model should have at least one var");
182+
183+
Assert.assertEquals(fruitModel.vars.size(), 3, "fruit model should have 3 vars (one for each oneOf item)");
184+
185+
for (CodegenProperty var : fruitModel.vars) {
186+
Assert.assertNotNull(var.name, "var name should not be null");
187+
Assert.assertNotNull(var.dataType, "var dataType should not be null");
188+
Assert.assertTrue(var.isModel, "var " + var.name + " should be a model type (isModel=" + var.isModel + ")");
189+
Assert.assertFalse(var.isContainer, "var should not be a container (it references a model)");
190+
191+
// Check expected properties based on discriminator title
192+
if (var.name.equals("apple_list")) {
193+
Assert.assertEquals(var.dataType, "StringArray", "apple_list should reference StringArray");
194+
} else if (var.name.equals("banana_map")) {
195+
Assert.assertEquals(var.dataType, "FloatMap", "banana_map should reference FloatMap");
196+
} else if (var.name.equals("orange_choice")) {
197+
Assert.assertEquals(var.dataType, "Orange", "orange_choice should reference Orange");
198+
} else {
199+
Assert.fail("Unexpected var name: " + var.name + ". Expected one of: apple_list, banana_map, orange_choice");
200+
}
201+
}
202+
203+
output.deleteOnExit();
204+
}
205+
145206
@Test(description = "convert a model with dollar signs")
146207
public void modelTest() {
147208
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/dollar-in-names-pull14359.yaml");
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
openapi: 3.1.0
2+
3+
info:
4+
title: fruity
5+
version: 0.0.1
6+
7+
paths:
8+
/:
9+
get:
10+
responses:
11+
'200':
12+
description: desc
13+
content:
14+
application/json:
15+
schema:
16+
$ref: '#/components/schemas/fruit'
17+
18+
components:
19+
schemas:
20+
fruit:
21+
oneOf:
22+
- title: appleList
23+
$ref: '#/components/schemas/appleArray'
24+
- title: bananaMap
25+
$ref: '#/components/schemas/bananaMap'
26+
- title: orangeChoice
27+
$ref: '#/components/schemas/orange'
28+
appleArray:
29+
type: array
30+
items:
31+
title: appleaArray
32+
type: string
33+
bananaMap:
34+
type: object
35+
additionalProperties:
36+
type: number
37+
orange:
38+
title: orange
39+
type: object
40+
properties:
41+
sweet:
42+
type: boolean

0 commit comments

Comments
 (0)