Skip to content

Commit 9dbf165

Browse files
committed
Wrap Complex Type
1 parent 298d6c2 commit 9dbf165

12 files changed

Lines changed: 1098 additions & 4 deletions

File tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
generatorName: protobuf-schema
22
outputDir: samples/config/petstore/protobuf-schema-config
3-
inputSpec: modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/protobuf/petstore-complex.yaml
44
templateDir: modules/openapi-generator/src/main/resources/protobuf-schema
55
additionalProperties:
66
packageName: petstore
77
addJsonNameAnnotation: true
88
numberedFieldNumberList: true
99
startEnumsWithUnspecified: true
10+
wrapComplexType: true

docs/generators/protobuf-schema.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
2121
|addJsonNameAnnotation|Append "json_name" annotation to message field when the specification name differs from the protobuf field name| |false|
2222
|numberedFieldNumberList|Field numbers in order.| |false|
2323
|startEnumsWithUnspecified|Introduces "UNSPECIFIED" as the first element of enumerations.| |false|
24+
|wrapComplexType|Generate Additional message for complex type| |false|
2425

2526
## IMPORT MAPPING
2627

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

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.openapitools.codegen.languages;
1818

19+
import io.swagger.v3.oas.models.OpenAPI;
20+
import io.swagger.v3.oas.models.media.ArraySchema;
21+
import io.swagger.v3.oas.models.media.MapSchema;
22+
import io.swagger.v3.oas.models.media.ObjectSchema;
1923
import io.swagger.v3.oas.models.media.Schema;
2024
import lombok.Setter;
2125
import org.apache.commons.lang3.StringUtils;
@@ -58,6 +62,8 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
5862

5963
public static final String ADD_JSON_NAME_ANNOTATION = "addJsonNameAnnotation";
6064

65+
public static final String WRAP_COMPLEX_TYPE = "wrapComplexType";
66+
6167
private final Logger LOGGER = LoggerFactory.getLogger(ProtobufSchemaCodegen.class);
6268

6369
@Setter protected String packageName = "openapitools";
@@ -68,6 +74,8 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
6874

6975
private boolean addJsonNameAnnotation = false;
7076

77+
private boolean wrapComplexType = false;
78+
7179
@Override
7280
public CodegenType getTag() {
7381
return CodegenType.SCHEMA;
@@ -177,6 +185,7 @@ public ProtobufSchemaCodegen() {
177185
addSwitch(NUMBERED_FIELD_NUMBER_LIST, "Field numbers in order.", numberedFieldNumberList);
178186
addSwitch(START_ENUMS_WITH_UNSPECIFIED, "Introduces \"UNSPECIFIED\" as the first element of enumerations.", startEnumsWithUnspecified);
179187
addSwitch(ADD_JSON_NAME_ANNOTATION, "Append \"json_name\" annotation to message field when the specification name differs from the protobuf field name", addJsonNameAnnotation);
188+
addSwitch(WRAP_COMPLEX_TYPE, "Generate Additional message for complex type", wrapComplexType);
180189
}
181190

182191
@Override
@@ -215,6 +224,10 @@ public void processOpts() {
215224
this.addJsonNameAnnotation = convertPropertyToBooleanAndWriteBack(ADD_JSON_NAME_ANNOTATION);
216225
}
217226

227+
if (additionalProperties.containsKey(this.WRAP_COMPLEX_TYPE)) {
228+
this.wrapComplexType = convertPropertyToBooleanAndWriteBack(WRAP_COMPLEX_TYPE);
229+
}
230+
218231
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
219232
}
220233

@@ -234,6 +247,190 @@ public String toOperationId(String operationId) {
234247
return camelize(sanitizeName(operationId));
235248
}
236249

250+
/**
251+
* Creates an array schema from the provided object schema.
252+
*/
253+
public Schema createArraySchema(Schema objectSchema) {
254+
ArraySchema arraySchema = new ArraySchema();
255+
arraySchema.items(objectSchema);
256+
return arraySchema;
257+
}
258+
259+
260+
/**
261+
* Creates a map schema from the provided object schema.
262+
*/
263+
public Schema createMapSchema(Schema objectSchema) {
264+
MapSchema mapSchema = new MapSchema();
265+
mapSchema.additionalProperties(objectSchema);
266+
return mapSchema;
267+
}
268+
269+
/**
270+
* Creates a new model schema for a property.
271+
*/
272+
273+
public Schema addSchemas(Schema schema, String schemaName, Set<Schema> visitedSchema) {
274+
LOGGER.info("Generating new model: {}", schemaName);
275+
276+
ObjectSchema model = new ObjectSchema();
277+
model.setName(schemaName);
278+
279+
Map<String, Schema> properties = new HashMap<>();
280+
properties.put(toVarName(schemaName), schema);
281+
model.setProperties(properties);
282+
283+
Schema refSchema = new Schema();
284+
refSchema.set$ref("#/components/schemas/" + schemaName);
285+
refSchema.setName(schemaName);
286+
287+
visitedSchema.add(refSchema);
288+
289+
openAPI.getComponents().addSchemas(schemaName, model);
290+
291+
return refSchema;
292+
}
293+
294+
public String getType(Schema schema) {
295+
if (!ModelUtils.isPrimitiveType(schema)) return "";
296+
if(ModelUtils.isNumberSchema(schema)) {
297+
if(schema.getFormat() != null) {
298+
return schema.getFormat();
299+
} else if (typeMapping.get(schema.getType()) != null) {
300+
return typeMapping.get(schema.getType());
301+
}
302+
}
303+
return ModelUtils.getType(schema);
304+
}
305+
306+
/**
307+
* Recursively generates schemas for nested maps and arrays
308+
*/
309+
public Schema generateNestedSchema(Schema schema, Set<Schema> visitedSchemas) {
310+
if (visitedSchemas.contains(schema)) {
311+
LOGGER.warn("Skipping recursive schema");
312+
return schema;
313+
}
314+
315+
if(ModelUtils.isArraySchema(schema)) {
316+
Schema itemsSchema = ModelUtils.getSchemaItems(schema);
317+
itemsSchema = ModelUtils.getReferencedSchema(openAPI, itemsSchema);
318+
if(ModelUtils.isModel(itemsSchema)) {
319+
String newSchemaName = ModelUtils.getSimpleRef(ModelUtils.getSchemaItems(schema).get$ref()) + ARRAY_SUFFIX;
320+
return addSchemas(schema, newSchemaName, visitedSchemas);
321+
}else if (ModelUtils.isPrimitiveType(itemsSchema)){
322+
String newSchemaName = getType(itemsSchema) + ARRAY_SUFFIX;
323+
return addSchemas(schema, newSchemaName, visitedSchemas);
324+
} else {
325+
Schema childSchema = generateNestedSchema(itemsSchema, visitedSchemas);
326+
String newSchemaName = childSchema.getName() + ARRAY_SUFFIX;
327+
Schema arrayModel = createArraySchema(childSchema);
328+
return addSchemas(arrayModel, newSchemaName, visitedSchemas);
329+
}
330+
} else if(ModelUtils.isMapSchema(schema)) {
331+
Schema mapValueSchema = ModelUtils.getAdditionalProperties(schema);
332+
mapValueSchema = ModelUtils.getReferencedSchema(openAPI, mapValueSchema);
333+
if(ModelUtils.isModel(mapValueSchema) ) {
334+
String newSchemaName = ModelUtils.getSimpleRef(ModelUtils.getAdditionalProperties(schema).get$ref()) + MAP_SUFFIX;
335+
return addSchemas(schema, newSchemaName, visitedSchemas);
336+
}else if (ModelUtils.isPrimitiveType(mapValueSchema)){
337+
String newSchemaName = getType(mapValueSchema) + MAP_SUFFIX;
338+
return addSchemas(schema, newSchemaName, visitedSchemas);
339+
} else {
340+
Schema innerSchema = generateNestedSchema(mapValueSchema, visitedSchemas);
341+
String newSchemaName = innerSchema.getName() + MAP_SUFFIX;
342+
Schema mapModel = createMapSchema(innerSchema);
343+
return addSchemas(mapModel, newSchemaName, visitedSchemas);
344+
}
345+
}
346+
return schema;
347+
}
348+
349+
public void processNestedSchemas(Schema schema, Set<Schema> visitedSchemas, String modelName) {
350+
if (ModelUtils.isMapSchema(schema) && ModelUtils.getAdditionalProperties(schema) != null) {
351+
Schema mapValueSchema = ModelUtils.getAdditionalProperties(schema);
352+
mapValueSchema = ModelUtils.getReferencedSchema(openAPI, mapValueSchema);
353+
if (ModelUtils.isArraySchema(mapValueSchema) || ModelUtils.isMapSchema(mapValueSchema)) {
354+
Schema innerSchema = generateNestedSchema(mapValueSchema, visitedSchemas);
355+
schema.setAdditionalProperties(innerSchema);
356+
357+
}
358+
} else if (ModelUtils.isArraySchema(schema) && ModelUtils.getSchemaItems(schema) != null) {
359+
Schema arrayItemSchema = ModelUtils.getSchemaItems(schema);
360+
arrayItemSchema = ModelUtils.getReferencedSchema(openAPI, arrayItemSchema);
361+
if (ModelUtils.isMapSchema(arrayItemSchema) || ModelUtils.isArraySchema(arrayItemSchema)) {
362+
Schema innerSchema = generateNestedSchema(arrayItemSchema, visitedSchemas);
363+
schema.setItems(innerSchema);
364+
}
365+
} else if (ModelUtils.isOneOf(schema) && schema.getOneOf() != null) {
366+
List<Schema> oneOfs = schema.getOneOf();
367+
List<Schema> newOneOfs = new ArrayList<>();
368+
for (Schema oneOf : oneOfs) {
369+
Schema oneOfSchema = ModelUtils.getReferencedSchema(openAPI, oneOf);
370+
if (ModelUtils.isArraySchema(oneOfSchema)) {
371+
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
372+
innerSchema.setTitle(oneOf.getTitle());
373+
newOneOfs.add(innerSchema);
374+
} else if (ModelUtils.isMapSchema(oneOfSchema)) {
375+
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
376+
innerSchema.setTitle(oneOf.getTitle());
377+
newOneOfs.add(innerSchema);
378+
} else {
379+
newOneOfs.add(oneOf);
380+
}
381+
}
382+
schema.setOneOf(newOneOfs);
383+
}
384+
}
385+
386+
private void wrapModels() {
387+
Map<String, Schema> models = openAPI.getComponents().getSchemas();
388+
Set<Schema> visitedSchema = new HashSet<>();
389+
List<String> modelNames = new ArrayList<String>(models.keySet());
390+
for (String modelName: modelNames) {
391+
Schema schema = models.get(modelName);
392+
processNestedSchemas(schema, visitedSchema, modelName);
393+
if (ModelUtils.isModel(schema) && schema.getProperties() != null) {
394+
Map<String, Schema> properties = schema.getProperties();
395+
for (Map.Entry<String, Schema> propertyEntry : properties.entrySet()) {
396+
Schema propertySchema = propertyEntry.getValue();
397+
processNestedSchemas(propertySchema, visitedSchema, modelName);
398+
}
399+
} else if (ModelUtils.isAllOf(schema)) {
400+
wrapComposedChildren(schema.getAllOf(), visitedSchema, modelName);
401+
} else if (ModelUtils.isOneOf(schema)) {
402+
wrapComposedChildren(schema.getOneOf(), visitedSchema, modelName);
403+
} else if (ModelUtils.isAnyOf(schema)) {
404+
wrapComposedChildren(schema.getAnyOf(), visitedSchema, modelName);
405+
}
406+
407+
}
408+
}
409+
410+
private void wrapComposedChildren(List<Schema> children, Set<Schema> visitedSchema, String modelName) {
411+
if (children == null || children.isEmpty()) {
412+
return;
413+
}
414+
for(Schema child: children) {
415+
child = ModelUtils.getReferencedSchema(openAPI, child);
416+
Map<String, Schema> properties = child.getProperties();
417+
if(properties == null || properties.isEmpty()) continue;
418+
for(Map.Entry<String, Schema> propertyEntry : properties.entrySet()) {
419+
Schema propertySchema = propertyEntry.getValue();
420+
processNestedSchemas(propertySchema, visitedSchema, modelName);
421+
}
422+
}
423+
}
424+
425+
@Override
426+
public void preprocessOpenAPI(OpenAPI openAPI) {
427+
super.preprocessOpenAPI(openAPI);
428+
if (wrapComplexType) {
429+
wrapModels();
430+
}
431+
}
432+
433+
237434
/**
238435
* Adds prefix to the enum allowable values
239436
* NOTE: Enum values use C++ scoping rules, meaning that enum values are siblings of their type, not children of it. Therefore, enum value must be unique

0 commit comments

Comments
 (0)