1616
1717package 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 ;
1923import io .swagger .v3 .oas .models .media .Schema ;
2024import lombok .Setter ;
2125import 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