30
30
import io .swagger .v3 .oas .models .responses .ApiResponse ;
31
31
import io .swagger .v3 .oas .models .responses .ApiResponses ;
32
32
import org .openapitools .codegen .utils .ModelUtils ;
33
+ import org .openapitools .codegen .utils .StringUtils ;
33
34
import org .slf4j .Logger ;
34
35
import org .slf4j .LoggerFactory ;
35
36
@@ -40,6 +41,7 @@ public class InlineModelResolver {
40
41
private OpenAPI openapi ;
41
42
private Map <String , Schema > addedModels = new HashMap <String , Schema >();
42
43
private Map <String , String > generatedSignature = new HashMap <String , String >();
44
+ public boolean resolveInlineEnums = false ;
43
45
44
46
// structure mapper sorts properties alphabetically on write to ensure models are
45
47
// serialized consistently for lookup of existing models
@@ -103,6 +105,209 @@ private void flattenPaths(OpenAPI openAPI) {
103
105
}
104
106
}
105
107
108
+ /**
109
+ * Return false if model can be represented by primitives e.g. string, object
110
+ * without properties, array or map of other model (model contanier), etc.
111
+ *
112
+ * Return true if a model should be generated e.g. object with properties,
113
+ * enum, oneOf, allOf, anyOf, etc.
114
+ *
115
+ * @param schema target schema
116
+ */
117
+ private boolean isModelNeeded (Schema schema ) {
118
+ if (resolveInlineEnums && schema .getEnum () != null && schema .getEnum ().size () > 0 ) {
119
+ return true ;
120
+ }
121
+ if (schema .getType () == null || "object" .equals (schema .getType ())) {
122
+ // object or undeclared type with properties
123
+ if (schema .getProperties () != null && schema .getProperties ().size () > 0 ) {
124
+ return true ;
125
+ }
126
+ }
127
+ if (schema instanceof ComposedSchema ) {
128
+ // allOf, anyOf, oneOf
129
+ ComposedSchema m = (ComposedSchema ) schema ;
130
+ if (m .getAllOf () != null && !m .getAllOf ().isEmpty ()) {
131
+ // check to ensure at least of the allOf item is model
132
+ for (Schema inner : m .getAllOf ()) {
133
+ if (isModelNeeded (inner )) {
134
+ return true ;
135
+ }
136
+ }
137
+ // allOf items are all non-model (e.g. type: string) only
138
+ return false ;
139
+ }
140
+ if (m .getAnyOf () != null && !m .getAnyOf ().isEmpty ()) {
141
+ return true ;
142
+ }
143
+ if (m .getOneOf () != null && !m .getOneOf ().isEmpty ()) {
144
+ return true ;
145
+ }
146
+ }
147
+
148
+ return false ;
149
+ }
150
+
151
+ /**
152
+ * Recursively gather inline models that need to be generated and
153
+ * replace inline schemas with $ref to schema to-be-generated.
154
+ *
155
+ * @param schema target schema
156
+ */
157
+ private void gatherInlineModels (Schema schema , String modelPrefix ) {
158
+ if (schema .get$ref () != null ) {
159
+ // if ref already, no inline schemas should be present but check for
160
+ // any to catch OpenAPI violations
161
+ if (isModelNeeded (schema ) || "object" .equals (schema .getType ()) ||
162
+ schema .getProperties () != null || schema .getAdditionalProperties () != null ||
163
+ schema instanceof ComposedSchema ) {
164
+ LOGGER .error ("Illegal schema found with $ref combined with other properties," +
165
+ " no properties should be defined alongside a $ref:\n " + schema .toString ());
166
+ }
167
+ return ;
168
+ }
169
+ // Check object models / any type models / composed models for properties,
170
+ // if the schema has a type defined that is not "object" it should not define
171
+ // any properties
172
+ if (schema .getType () == null || "object" .equals (schema .getType ())) {
173
+ // Check properties and recurse, each property could be its own inline model
174
+ Map <String , Schema > props = schema .getProperties ();
175
+ if (props != null ) {
176
+ for (String propName : props .keySet ()) {
177
+ Schema prop = props .get (propName );
178
+ // Recurse to create $refs for inner models
179
+ //gatherInlineModels(prop, modelPrefix + StringUtils.camelize(propName));
180
+ gatherInlineModels (prop , modelPrefix + "_" + propName );
181
+ if (isModelNeeded (prop )) {
182
+ // If this schema should be split into its own model, do so
183
+ //Schema refSchema = this.makeSchemaResolve(modelPrefix, StringUtils.camelize(propName), prop);
184
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_" + propName , prop );
185
+ props .put (propName , refSchema );
186
+ } else if (prop instanceof ComposedSchema ) {
187
+ ComposedSchema m = (ComposedSchema ) prop ;
188
+ if (m .getAllOf () != null && m .getAllOf ().size () == 1 &&
189
+ !(m .getAllOf ().get (0 ).getType () == null || "object" .equals (m .getAllOf ().get (0 ).getType ()))) {
190
+ // allOf with only 1 type (non-model)
191
+ LOGGER .info ("allOf schema used by the property `{}` replaced by its only item (a type)" , propName );
192
+ props .put (propName , m .getAllOf ().get (0 ));
193
+ }
194
+ }
195
+ }
196
+ }
197
+ // Check additionalProperties for inline models
198
+ if (schema .getAdditionalProperties () != null ) {
199
+ if (schema .getAdditionalProperties () instanceof Schema ) {
200
+ Schema inner = (Schema ) schema .getAdditionalProperties ();
201
+ // Recurse to create $refs for inner models
202
+ gatherInlineModels (inner , modelPrefix + "_addl_props" );
203
+ if (isModelNeeded (inner )) {
204
+ // If this schema should be split into its own model, do so
205
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_addl_props" , inner );
206
+ schema .setAdditionalProperties (refSchema );
207
+ }
208
+ }
209
+ }
210
+ } else if (schema .getProperties () != null ) {
211
+ // If non-object type is specified but also properties
212
+ LOGGER .error ("Illegal schema found with non-object type combined with properties," +
213
+ " no properties should be defined:\n " + schema .toString ());
214
+ return ;
215
+ } else if (schema .getAdditionalProperties () != null ) {
216
+ // If non-object type is specified but also additionalProperties
217
+ LOGGER .error ("Illegal schema found with non-object type combined with" +
218
+ " additionalProperties, no additionalProperties should be defined:\n " +
219
+ schema .toString ());
220
+ return ;
221
+ }
222
+ // Check array items
223
+ if (schema instanceof ArraySchema ) {
224
+ ArraySchema array = (ArraySchema ) schema ;
225
+ Schema items = array .getItems ();
226
+ if (items == null ) {
227
+ LOGGER .error ("Illegal schema found with array type but no items," +
228
+ " items must be defined for array schemas:\n " + schema .toString ());
229
+ return ;
230
+ }
231
+ // Recurse to create $refs for inner models
232
+ gatherInlineModels (items , modelPrefix + "Items" );
233
+
234
+ if (isModelNeeded (items )) {
235
+ // If this schema should be split into its own model, do so
236
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_inner" , items );
237
+ array .setItems (refSchema );
238
+ }
239
+ }
240
+ // Check allOf, anyOf, oneOf for inline models
241
+ if (schema instanceof ComposedSchema ) {
242
+ ComposedSchema m = (ComposedSchema ) schema ;
243
+ if (m .getAllOf () != null ) {
244
+ List <Schema > newAllOf = new ArrayList <Schema >();
245
+ boolean atLeastOneModel = false ;
246
+ for (Schema inner : m .getAllOf ()) {
247
+ // Recurse to create $refs for inner models
248
+ gatherInlineModels (inner , modelPrefix + "_allOf" );
249
+ if (isModelNeeded (inner )) {
250
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_allOf" , inner );
251
+ newAllOf .add (refSchema ); // replace with ref
252
+ atLeastOneModel = true ;
253
+ } else {
254
+ newAllOf .add (inner );
255
+ }
256
+ }
257
+ if (atLeastOneModel ) {
258
+ m .setAllOf (newAllOf );
259
+ } else {
260
+ // allOf is just one or more types only so do not generate the inline allOf model
261
+ if (m .getAllOf ().size () == 1 ) {
262
+ // handle earlier in this function when looping through properites
263
+ } else if (m .getAllOf ().size () > 1 ) {
264
+ LOGGER .warn ("allOf schema `{}` containing multiple types (not model) is not supported at the moment." , schema .getName ());
265
+ } else {
266
+ LOGGER .error ("allOf schema `{}` contains no items." , schema .getName ());
267
+ }
268
+ }
269
+ }
270
+ if (m .getAnyOf () != null ) {
271
+ List <Schema > newAnyOf = new ArrayList <Schema >();
272
+ for (Schema inner : m .getAnyOf ()) {
273
+ // Recurse to create $refs for inner models
274
+ gatherInlineModels (inner , modelPrefix + "_anyOf" );
275
+ if (isModelNeeded (inner )) {
276
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_anyOf" , inner );
277
+ newAnyOf .add (refSchema ); // replace with ref
278
+ } else {
279
+ newAnyOf .add (inner );
280
+ }
281
+ }
282
+ m .setAnyOf (newAnyOf );
283
+ }
284
+ if (m .getOneOf () != null ) {
285
+ List <Schema > newOneOf = new ArrayList <Schema >();
286
+ for (Schema inner : m .getOneOf ()) {
287
+ // Recurse to create $refs for inner models
288
+ gatherInlineModels (inner , modelPrefix + "_oneOf" );
289
+ if (isModelNeeded (inner )) {
290
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_oneOf" , inner );
291
+ newOneOf .add (refSchema ); // replace with ref
292
+ } else {
293
+ newOneOf .add (inner );
294
+ }
295
+ }
296
+ m .setOneOf (newOneOf );
297
+ }
298
+ }
299
+ // Check not schema
300
+ if (schema .getNot () != null ) {
301
+ Schema not = schema .getNot ();
302
+ // Recurse to create $refs for inner models
303
+ gatherInlineModels (not , modelPrefix + "_not" );
304
+ if (isModelNeeded (not )) {
305
+ Schema refSchema = this .makeSchemaResolve (modelPrefix , "_not" , not );
306
+ schema .setNot (refSchema );
307
+ }
308
+ }
309
+ }
310
+
106
311
/**
107
312
* Flatten inline models in RequestBody
108
313
*
@@ -432,11 +637,13 @@ private void flattenComponents(OpenAPI openAPI) {
432
637
flattenComposedChildren (openAPI , modelName + "_anyOf" , m .getAnyOf ());
433
638
flattenComposedChildren (openAPI , modelName + "_oneOf" , m .getOneOf ());
434
639
} else if (model instanceof Schema ) {
435
- Schema m = model ;
436
- Map <String , Schema > properties = m .getProperties ();
437
- flattenProperties (openAPI , properties , modelName );
438
- fixStringModel (m );
439
- } else if (ModelUtils .isArraySchema (model )) {
640
+ //Schema m = model;
641
+ //Map<String, Schema> properties = m.getProperties();
642
+ //flattenProperties(openAPI, properties, modelName);
643
+ //fixStringModel(m);
644
+ gatherInlineModels (model , modelName );
645
+
646
+ } /*else if (ModelUtils.isArraySchema(model)) {
440
647
ArraySchema m = (ArraySchema) model;
441
648
Schema inner = m.getItems();
442
649
if (inner instanceof ObjectSchema) {
@@ -458,7 +665,7 @@ private void flattenComponents(OpenAPI openAPI) {
458
665
}
459
666
}
460
667
}
461
- }
668
+ }*/
462
669
}
463
670
}
464
671
@@ -690,7 +897,8 @@ private Schema modelFromProperty(OpenAPI openAPI, Schema object, String path) {
690
897
model .setExtensions (object .getExtensions ());
691
898
model .setExclusiveMinimum (object .getExclusiveMinimum ());
692
899
model .setExclusiveMaximum (object .getExclusiveMaximum ());
693
- model .setExample (object .getExample ());
900
+ // no need to set it again as it's set earlier
901
+ //model.setExample(object.getExample());
694
902
model .setDeprecated (object .getDeprecated ());
695
903
696
904
if (properties != null ) {
@@ -700,6 +908,48 @@ private Schema modelFromProperty(OpenAPI openAPI, Schema object, String path) {
700
908
return model ;
701
909
}
702
910
911
+ /**
912
+ * Resolve namespace conflicts using:
913
+ * title (if title exists) or
914
+ * prefix + suffix (if title not specified)
915
+ * @param prefix used to form name if no title found in schema
916
+ * @param suffix used to form name if no title found in schema
917
+ * @param schema title property used to form name if exists and schema definition used
918
+ * to create new schema if doesn't exist
919
+ * @return a new schema or $ref to an existing one if it was already created
920
+ */
921
+ private Schema makeSchemaResolve (String prefix , String suffix , Schema schema ) {
922
+ if (schema .getTitle () == null ) {
923
+ return makeSchemaInComponents (uniqueName (sanitizeName (prefix + suffix )), schema );
924
+ }
925
+ return makeSchemaInComponents (uniqueName (sanitizeName (schema .getTitle ())), schema );
926
+ }
927
+
928
+ /**
929
+ * Move schema to components (if new) and return $ref to schema or
930
+ * existing schema.
931
+ *
932
+ * @param name new schema name
933
+ * @param schema schema to move to components or find existing ref
934
+ * @return {@link Schema} $ref schema to new or existing schema
935
+ */
936
+ private Schema makeSchemaInComponents (String name , Schema schema ) {
937
+ String existing = matchGenerated (schema );
938
+ Schema refSchema ;
939
+ if (existing != null ) {
940
+ refSchema = new Schema ().$ref (existing );
941
+ } else {
942
+ if (resolveInlineEnums && schema .getEnum () != null && schema .getEnum ().size () > 0 ) {
943
+ LOGGER .warn ("Model " + name + " promoted to its own schema due to resolveInlineEnums=true" );
944
+ }
945
+ refSchema = new Schema ().$ref (name );
946
+ addGenerated (name , schema );
947
+ openapi .getComponents ().addSchemas (name , schema );
948
+ }
949
+ this .copyVendorExtensions (schema , refSchema );
950
+ return refSchema ;
951
+ }
952
+
703
953
/**
704
954
* Make a Schema
705
955
*
0 commit comments