Skip to content

Commit ec5401b

Browse files
committed
Improve Protobuf Generator's oneOf Handling allOf Unwrapping with Complex Types
1 parent e7287d1 commit ec5401b

3 files changed

Lines changed: 141 additions & 8 deletions

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,23 @@ 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().isEmpty()) {
447+
if (oneOf.getAllOf().size() == 1) {
448+
Object allOfObj = oneOf.getAllOf().get(0);
449+
if (allOfObj instanceof Schema) {
450+
Schema allOfItem = (Schema) allOfObj;
451+
if (StringUtils.isNotEmpty(allOfItem.get$ref())) {
452+
oneOfSchema = ModelUtils.getReferencedSchema(openAPI, allOfItem);
453+
}
454+
}
455+
} else {
456+
LOGGER.warn("oneOf item has allOf with multiple items, which is not supported.");
457+
}
458+
} else {
459+
oneOfSchema = ModelUtils.getReferencedSchema(openAPI, oneOf);
460+
}
461+
446462
if (ModelUtils.isArraySchema(oneOfSchema)) {
447463
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
448464
innerSchema.setTitle(oneOf.getTitle());

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

Lines changed: 82 additions & 7 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;
@@ -44,6 +46,8 @@
4446
import java.util.Arrays;
4547
import java.util.List;
4648
import java.util.Map;
49+
import java.util.HashMap;
50+
4751

4852
import static org.openapitools.codegen.TestUtils.createCodegenModelWrapper;
4953
import static org.openapitools.codegen.languages.ProtobufSchemaCodegen.USE_SIMPLIFIED_ENUM_NAMES;
@@ -98,12 +102,51 @@ private void assertFileEquals(Path generatedFilePath, Path expectedFilePath) thr
98102
public void testCodeGenWithPrimitiveOneOf() throws IOException {
99103
// set line break to \n across all platforms
100104
System.setProperty("line.separator", "\n");
105+
Map<String, String> importmapping = new HashMap<>();
106+
Map<String, String> typemapping = new HashMap<>();
107+
typemapping.put("AnyType", "ObjectMap");
108+
typemapping.put("object", "ObjectMap");
109+
typemapping.put("bytes", "bytes");
110+
typemapping.put("NullValue", "NullValue");
111+
typemapping.put("number", "GeneralNumber");
112+
File output = Files.createTempDirectory("test").toFile();
113+
Map<String, Object> additionalProperties = new HashMap<>();
114+
additionalProperties.put("numberedFieldNumberList", true);
115+
additionalProperties.put("aggregateModelsName", "aaaa");
116+
additionalProperties.put("supportMultipleResponses", false);
117+
Map<String, String> inline = new HashMap<>();
118+
Map<String, String> globalProperty = new HashMap<>();
119+
globalProperty.put("skipFormModel", "true");
120+
inline.put("RESOLVE_INLINE_ENUMS", "true");
121+
inline.put("SKIP_SCHEMA_REUSE", "true");
122+
final CodegenConfigurator configurator = new CodegenConfigurator()
123+
.setGeneratorName("protobuf-schema")
124+
.setInputSpec("src/test/resources/3_0/oneOf.yaml")
125+
.setAdditionalProperties(additionalProperties)
126+
.setTypeMappings(typemapping)
127+
.setImportMappings(importmapping)
128+
.setGlobalProperties(globalProperty)
129+
.setInlineSchemaOptions(inline)
130+
.setValidateSpec(false)
131+
.setOutputDir("/home/user/openapi-generator/test/");
132+
133+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
134+
DefaultGenerator generator = new DefaultGenerator();
135+
List<File> files = generator.opts(clientOptInput).generate();
136+
137+
output.deleteOnExit();
138+
}
139+
140+
@Test
141+
public void testCodeGenWithPrimitiveAnyOf() throws IOException {
142+
// set line break to \n across all platforms
143+
System.setProperty("line.separator", "\n");
101144

102145
File output = Files.createTempDirectory("test").toFile();
103146

104147
final CodegenConfigurator configurator = new CodegenConfigurator()
105148
.setGeneratorName("protobuf-schema")
106-
.setInputSpec("src/test/resources/3_0/oneOf.yaml")
149+
.setInputSpec("src/test/resources/3_0/anyOf.yaml")
107150
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
108151

109152
final ClientOptInput clientOptInput = configurator.toClientOptInput();
@@ -113,31 +156,63 @@ public void testCodeGenWithPrimitiveOneOf() throws IOException {
113156
TestUtils.ensureContainsFile(files, output, "models/fruit.proto");
114157
Path path = Paths.get(output + "/models/fruit.proto");
115158

116-
assertFileEquals(path, Paths.get("src/test/resources/3_0/protobuf-schema/fruitOneOf.proto"));
159+
assertFileEquals(path, Paths.get("src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto"));
117160

118161
output.deleteOnExit();
119162
}
120163

121164
@Test
122-
public void testCodeGenWithPrimitiveAnyOf() throws IOException {
123-
// set line break to \n across all platforms
165+
public void testCodeGenWithOneOfDiscriminator31() throws IOException {
124166
System.setProperty("line.separator", "\n");
125167

126168
File output = Files.createTempDirectory("test").toFile();
127169

128170
final CodegenConfigurator configurator = new CodegenConfigurator()
129171
.setGeneratorName("protobuf-schema")
130-
.setInputSpec("src/test/resources/3_0/anyOf.yaml")
172+
.setInputSpec("src/test/resources/3_1/oneOf.yaml")
131173
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
132174

133175
final ClientOptInput clientOptInput = configurator.toClientOptInput();
134176
DefaultGenerator generator = new DefaultGenerator();
135177
List<File> files = generator.opts(clientOptInput).generate();
136178

137179
TestUtils.ensureContainsFile(files, output, "models/fruit.proto");
138-
Path path = Paths.get(output + "/models/fruit.proto");
139180

140-
assertFileEquals(path, Paths.get("src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto"));
181+
// Get the processed OpenAPI with wrapper schemas
182+
OpenAPI openAPI = clientOptInput.getOpenAPI();
183+
ProtobufSchemaCodegen codegen = new ProtobufSchemaCodegen();
184+
codegen.setOpenAPI(openAPI);
185+
codegen.processOpts();
186+
187+
Schema fruitSchema = openAPI.getComponents().getSchemas().get("fruit");
188+
Assert.assertNotNull(fruitSchema, "fruit schema should exist");
189+
190+
CodegenModel fruitModel = codegen.fromModel("fruit", fruitSchema);
191+
codegen.postProcessModels(createCodegenModelWrapper(fruitModel));
192+
193+
Assert.assertNotNull(fruitModel.oneOf, "fruit model should have oneOf items");
194+
Assert.assertTrue(fruitModel.oneOf.size() >= 2, "fruit model should have at least 2 oneOf items");
195+
196+
Assert.assertNotNull(fruitModel.vars, "fruit model should have vars");
197+
Assert.assertTrue(fruitModel.vars.size() > 0, "fruit model should have at least one var");
198+
199+
Assert.assertEquals(fruitModel.vars.size(), 3, "fruit model should have 3 vars (one for each oneOf item)");
200+
201+
for (CodegenProperty var : fruitModel.vars) {
202+
Assert.assertNotNull(var.name, "var name should not be null");
203+
Assert.assertNotNull(var.dataType, "var dataType should not be null");
204+
Assert.assertTrue(var.isModel, "var should be a model type");
205+
Assert.assertFalse(var.isContainer, "var should not be a container (it references a model)");
206+
207+
// Check expected properties based on discriminator title
208+
if (var.name.equals("apple_list")) {
209+
Assert.assertEquals(var.dataType, "StringArray", "apple_list should reference StringArray");
210+
} else if (var.name.equals("banana_map")) {
211+
Assert.assertEquals(var.dataType, "FloatMap", "banana_map should reference FloatMap");
212+
} else if (var.name.equals("orange_choice")) {
213+
Assert.assertEquals(var.dataType, "Orange", "orange_choice should reference Orange");
214+
}
215+
}
141216

142217
output.deleteOnExit();
143218
}
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)