Skip to content

Commit 8330e16

Browse files
authored
Better inline model resolver to handle inline schema in array item (#12104)
* better support of inline schema in array item * update tests * update samples * regenerate samples * fix allof naming, remove files * add files * update samples * update readme * fix tests * update samples * update samples * add new files * update test spec * add back tests * remove unused files * comment out python test * update js test using own spec * remove files * remove unused files * remove files * remove unused files * better handling of allOf with a single type * comment out go test * remove test_all_of_with_single_ref_single_ref_type.py * fix inline resolver, uncomment go test
1 parent 12454de commit 8330e16

File tree

200 files changed

+7499
-979
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

200 files changed

+7499
-979
lines changed

bin/configs/javascript-es6.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
generatorName: javascript
22
outputDir: samples/client/petstore/javascript-es6
3-
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/javascript/petstore-with-fake-endpoints-models-for-testing.yaml
44
templateDir: modules/openapi-generator/src/main/resources/Javascript/es6
55
additionalProperties:
66
appName: PetstoreClient

bin/configs/javascript-promise-es6.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
generatorName: javascript
22
outputDir: samples/client/petstore/javascript-promise-es6
3-
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/javascript/petstore-with-fake-endpoints-models-for-testing.yaml
44
templateDir: modules/openapi-generator/src/main/resources/Javascript/es6
55
additionalProperties:
66
usePromises: "true"

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

Lines changed: 257 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import io.swagger.v3.oas.models.responses.ApiResponse;
3131
import io.swagger.v3.oas.models.responses.ApiResponses;
3232
import org.openapitools.codegen.utils.ModelUtils;
33+
import org.openapitools.codegen.utils.StringUtils;
3334
import org.slf4j.Logger;
3435
import org.slf4j.LoggerFactory;
3536

@@ -40,6 +41,7 @@ public class InlineModelResolver {
4041
private OpenAPI openapi;
4142
private Map<String, Schema> addedModels = new HashMap<String, Schema>();
4243
private Map<String, String> generatedSignature = new HashMap<String, String>();
44+
public boolean resolveInlineEnums = false;
4345

4446
// structure mapper sorts properties alphabetically on write to ensure models are
4547
// serialized consistently for lookup of existing models
@@ -103,6 +105,209 @@ private void flattenPaths(OpenAPI openAPI) {
103105
}
104106
}
105107

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+
106311
/**
107312
* Flatten inline models in RequestBody
108313
*
@@ -432,11 +637,13 @@ private void flattenComponents(OpenAPI openAPI) {
432637
flattenComposedChildren(openAPI, modelName + "_anyOf", m.getAnyOf());
433638
flattenComposedChildren(openAPI, modelName + "_oneOf", m.getOneOf());
434639
} 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)) {
440647
ArraySchema m = (ArraySchema) model;
441648
Schema inner = m.getItems();
442649
if (inner instanceof ObjectSchema) {
@@ -458,7 +665,7 @@ private void flattenComponents(OpenAPI openAPI) {
458665
}
459666
}
460667
}
461-
}
668+
}*/
462669
}
463670
}
464671

@@ -690,7 +897,8 @@ private Schema modelFromProperty(OpenAPI openAPI, Schema object, String path) {
690897
model.setExtensions(object.getExtensions());
691898
model.setExclusiveMinimum(object.getExclusiveMinimum());
692899
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());
694902
model.setDeprecated(object.getDeprecated());
695903

696904
if (properties != null) {
@@ -700,6 +908,48 @@ private Schema modelFromProperty(OpenAPI openAPI, Schema object, String path) {
700908
return model;
701909
}
702910

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+
703953
/**
704954
* Make a Schema
705955
*

0 commit comments

Comments
 (0)