Add isCircularReference to properties (#4553)

This commit is contained in:
Erik Timmers
2019-12-10 14:43:16 +01:00
committed by William Cheng
parent 00abb4780c
commit a695748805
5 changed files with 143 additions and 3 deletions
@@ -62,6 +62,7 @@ public class CodegenProperty implements Cloneable {
public boolean isWriteOnly;
public boolean isNullable;
public boolean isSelfReference;
public boolean isCircularReference;
public List<String> _enum;
public Map<String, Object> allowableValues;
public CodegenProperty items;
@@ -498,6 +499,7 @@ public class CodegenProperty implements Cloneable {
sb.append(", isWriteOnly=").append(isWriteOnly);
sb.append(", isNullable=").append(isNullable);
sb.append(", isSelfReference=").append(isSelfReference);
sb.append(", isCircularReference=").append(isCircularReference);
sb.append(", _enum=").append(_enum);
sb.append(", allowableValues=").append(allowableValues);
sb.append(", items=").append(items);
@@ -558,6 +560,7 @@ public class CodegenProperty implements Cloneable {
isWriteOnly == that.isWriteOnly &&
isNullable == that.isNullable &&
isSelfReference == that.isSelfReference &&
isCircularReference == that.isCircularReference &&
hasValidation == that.hasValidation &&
isInherited == that.isInherited &&
isXmlAttribute == that.isXmlAttribute &&
@@ -613,8 +616,9 @@ public class CodegenProperty implements Cloneable {
hasMoreNonReadOnly, isPrimitiveType, isModel, isContainer, isString, isNumeric, isInteger,
isLong, isNumber, isFloat, isDouble, isByteArray, isBinary, isFile, isBoolean, isDate, isDateTime,
isUuid, isUri, isEmail, isFreeFormObject, isListContainer, isMapContainer, isEnum, isReadOnly,
isWriteOnly, isNullable, isSelfReference, _enum, allowableValues, items, mostInnerItems,
vendorExtensions, hasValidation, isInherited, discriminatorValue, nameInCamelCase, nameInSnakeCase,
enumName, maxItems, minItems, isXmlAttribute, xmlPrefix, xmlName, xmlNamespace, isXmlWrapped);
isWriteOnly, isNullable, isSelfReference, isCircularReference, _enum, allowableValues, items,
mostInnerItems, vendorExtensions, hasValidation, isInherited, discriminatorValue, nameInCamelCase,
nameInSnakeCase, enumName, maxItems, minItems, isXmlAttribute, xmlPrefix, xmlName, xmlNamespace,
isXmlWrapped);
}
}
@@ -328,10 +328,61 @@ public class DefaultCodegen implements CodegenConfig {
}
}
}
setCircularReferences(allModels);
return objs;
}
public void setCircularReferences(Map<String, CodegenModel> models) {
final Map<String, List<CodegenProperty>> dependencyMap = models.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(entry.getValue())));
models.keySet().forEach(name -> setCircularReferencesOnProperties(name, dependencyMap));
}
private List<CodegenProperty> getModelDependencies(CodegenModel model) {
return model.getAllVars().stream()
.map(prop -> {
if (prop.isContainer) {
return prop.items.dataType == null ? null : prop;
}
return prop.dataType == null ? null : prop;
})
.filter(prop -> prop != null)
.collect(Collectors.toList());
}
private void setCircularReferencesOnProperties(final String root,
final Map<String, List<CodegenProperty>> dependencyMap) {
dependencyMap.getOrDefault(root, new ArrayList<>()).stream()
.forEach(prop -> {
final List<String> unvisited =
Collections.singletonList(prop.isContainer ? prop.items.dataType : prop.dataType);
prop.isCircularReference = isCircularReference(root,
new HashSet<>(),
new ArrayList<>(unvisited),
dependencyMap);
});
}
private boolean isCircularReference(final String root,
final Set<String> visited,
final List<String> unvisited,
final Map<String, List<CodegenProperty>> dependencyMap) {
for (int i = 0; i < unvisited.size(); i++) {
final String next = unvisited.get(i);
if (!visited.contains(next)) {
if (next.equals(root)) {
return true;
}
dependencyMap.getOrDefault(next, new ArrayList<>())
.forEach(prop -> unvisited.add(prop.isContainer ? prop.items.dataType : prop.dataType));
visited.add(next);
}
}
return false;
}
// override with any special post-processing
@SuppressWarnings("static-method")
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
@@ -347,6 +347,7 @@ public class ElmClientCodegen extends DefaultCodegen implements CodegenConfig {
});
}
}
setCircularReferences(allModels);
for (Map.Entry<String, Object> entry : objs.entrySet()) {
Map<String, Object> inner = (Map<String, Object>) entry.getValue();
List<Map<String, Object>> models = (List<Map<String, Object>>) inner.get("models");
@@ -1113,4 +1113,55 @@ public class DefaultCodegenTest {
Assert.assertFalse(result);
}
}
@Test
public void testCircularReferencesDetection() {
// given
DefaultCodegen codegen = new DefaultCodegen();
final CodegenProperty inboundOut = new CodegenProperty();
inboundOut.baseName = "out";
inboundOut.dataType = "RoundA";
final CodegenProperty roundANext = new CodegenProperty();
roundANext.baseName = "next";
roundANext.dataType = "RoundB";
final CodegenProperty roundBNext = new CodegenProperty();
roundBNext.baseName = "next";
roundBNext.dataType = "RoundC";
final CodegenProperty roundCNext = new CodegenProperty();
roundCNext.baseName = "next";
roundCNext.dataType = "RoundA";
final CodegenProperty roundCOut = new CodegenProperty();
roundCOut.baseName = "out";
roundCOut.dataType = "Outbound";
final CodegenModel inboundModel = new CodegenModel();
inboundModel.setDataType("Inbound");
inboundModel.setAllVars(Collections.singletonList(inboundOut));
final CodegenModel roundAModel = new CodegenModel();
roundAModel.setDataType("RoundA");
roundAModel.setAllVars(Collections.singletonList(roundANext));
final CodegenModel roundBModel = new CodegenModel();
roundBModel.setDataType("RoundB");
roundBModel.setAllVars(Collections.singletonList(roundBNext));
final CodegenModel roundCModel = new CodegenModel();
roundCModel.setDataType("RoundC");
roundCModel.setAllVars(Arrays.asList(roundCNext, roundCOut));
final CodegenModel outboundModel = new CodegenModel();
outboundModel.setDataType("Outbound");
final Map<String, CodegenModel> models = new HashMap<>();
models.put("Inbound", inboundModel);
models.put("RoundA", roundAModel);
models.put("RoundB", roundBModel);
models.put("RoundC", roundCModel);
models.put("Outbound", outboundModel);
// when
codegen.setCircularReferences(models);
// then
Assert.assertFalse(inboundOut.isCircularReference);
Assert.assertTrue(roundANext.isCircularReference);
Assert.assertTrue(roundBNext.isCircularReference);
Assert.assertTrue(roundCNext.isCircularReference);
Assert.assertFalse(roundCOut.isCircularReference);
}
}
@@ -0,0 +1,33 @@
openapi: 3.0.0
info:
description: Test
version: 1.0.0
title: OpenAPI
paths:
/foo:
post:
description: ''
responses:
'200':
description: Response
content:
application/json:
schema:
$ref: '#/components/schemas/Foo'
components:
schemas:
Foo:
type: object
properties:
foo:
$ref: '#/components/schemas/Foo'
Bar:
type: object
properties:
baz:
$ref: '#/components/schemas/Baz'
Baz:
type: object
properties:
bar:
$ref: '#/components/schemas/Bar'