Add new Plantuml generator for creating a schema diagram (#6255)

* add scaffolded new documentation generator files

* add openapi3 plantuml bin scripts

* change plantuml codegen to only generate a schemas.plantuml file

* initial plantuml schema diagram

* add item type to Lists

* add inheritance relationships

* add list one-to-many relationships

* add newline between model definitions and relationships

* add composition data type relationship

* remove allOf models and interface references

* add new entities data to SupportingFileData

* add List dataType support to entity fields

* remove composed types and remove allOf suffix from inline types

* add inheritances to supporting files data object

* add aggregation relationships to supporting file data

* add isList to compisition relationships

* refactor PlantumlDocumentationCodegenTest

* add property name to relationships

* remove old code form PlantumlDocumentationCodegen

* add plantuml generator sample output

* remove use of javafx.util.Pair

* fix casing of complex data type for fields

* add plantuml generator docs

* fix bug caused by assumption that inline _allOf types will always be unique but apparently they can be shared if they have identical properties!

* fix bug with missing relationships caused by shared identical _allOf schemas

Co-authored-by: Patrick.Burls <patrick.burls@bskyb.com>
This commit is contained in:
Patrick Burls 2020-05-14 11:20:26 +01:00 committed by GitHub
parent 9f95f0cf3d
commit f03458dde4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 837 additions and 0 deletions

View File

@ -0,0 +1,31 @@
#!/bin/sh
SCRIPT="$0"
while [ -h "$SCRIPT" ] ; do
ls=$(ls -ld "$SCRIPT")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=$(dirname "$SCRIPT")/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=$(dirname "$SCRIPT")/..
APP_DIR=$(cd "${APP_DIR}"; pwd)
fi
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
if [ ! -f "$executable" ]
then
mvn clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="$@ generate -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g plantuml -o samples/documentation/petstore/plantuml"
java ${JAVA_OPTS} -jar ${executable} ${ags}

View File

@ -0,0 +1,10 @@
set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
If Not Exist %executable% (
mvn clean package
)
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
set ags=generate --artifact-id "plantuml-petstore-documentation" -i modules\openapi-generator\src\test\resources\3_0\petstore.yaml -g plantuml -o samples\documentation\petstore\plantuml
java %JAVA_OPTS% -jar %executable% %ags%

View File

@ -0,0 +1,31 @@
#!/bin/sh
SCRIPT="$0"
while [ -h "$SCRIPT" ] ; do
ls=$(ls -ld "$SCRIPT")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=$(dirname "$SCRIPT")/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=$(dirname "$SCRIPT")/..
APP_DIR=$(cd "${APP_DIR}"; pwd)
fi
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
if [ ! -f "$executable" ]
then
mvn clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g plantuml -o samples/documentation/petstore/plantuml"
java ${JAVA_OPTS} -jar ${executable} ${ags}

View File

@ -0,0 +1,10 @@
set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
If Not Exist %executable% (
mvn clean package
)
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
set ags=generate --artifact-id "plantuml-petstore-documentation" -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g plantuml -o samples\documentation\petstore\plantuml
java %JAVA_OPTS% -jar %executable% %ags%

View File

@ -136,6 +136,7 @@ The following generators are available:
* [markdown (beta)](generators/markdown.md)
* [openapi](generators/openapi.md)
* [openapi-yaml](generators/openapi-yaml.md)
* [plantuml](generators/plantuml.md)
## SCHEMA generators

159
docs/generators/plantuml.md Normal file
View File

@ -0,0 +1,159 @@
---
title: Config Options for plantuml
sidebar_label: plantuml
---
| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|legacyDiscriminatorBehavior|This flag is used by OpenAPITools codegen to influence the processing of the discriminator attribute in OpenAPI documents. This flag has no impact if the OAS document does not use the discriminator attribute. The default value of this flag is set in each language-specific code generator (e.g. Python, Java, go...)using the method toModelName. Note to developers supporting a language generator in OpenAPITools; to fully support the discriminator attribute as defined in the OAS specification 3.x, language generators should set this flag to true by default; however this requires updating the mustache templates to generate a language-specific discriminator lookup function that iterates over {{#mappedModels}} and does not iterate over {{children}}, {{#anyOf}}, or {{#oneOf}}.|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
## IMPORT MAPPING
| Type/Alias | Imports |
| ---------- | ------- |
|Array|java.util.List|
|ArrayList|java.util.ArrayList|
|BigDecimal|java.math.BigDecimal|
|Date|java.util.Date|
|DateTime|org.joda.time.*|
|File|java.io.File|
|HashMap|java.util.HashMap|
|List|java.util.*|
|LocalDate|org.joda.time.*|
|LocalDateTime|org.joda.time.*|
|LocalTime|org.joda.time.*|
|Map|java.util.Map|
|Set|java.util.*|
|Timestamp|java.sql.Timestamp|
|URI|java.net.URI|
|UUID|java.util.UUID|
## INSTANTIATION TYPES
| Type/Alias | Instantiated By |
| ---------- | --------------- |
## LANGUAGE PRIMITIVES
<ul class="column-ul">
</ul>
## RESERVED WORDS
<ul class="column-ul">
</ul>
## FEATURE SET
### Client Modification Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasePath|✗|ToolingExtension
|Authorizations|✗|ToolingExtension
|UserAgent|✗|ToolingExtension
### Data Type Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Custom|✗|OAS2,OAS3
|Int32|✓|OAS2,OAS3
|Int64|✓|OAS2,OAS3
|Float|✓|OAS2,OAS3
|Double|✓|OAS2,OAS3
|Decimal|✓|ToolingExtension
|String|✓|OAS2,OAS3
|Byte|✓|OAS2,OAS3
|Binary|✓|OAS2,OAS3
|Boolean|✓|OAS2,OAS3
|Date|✓|OAS2,OAS3
|DateTime|✓|OAS2,OAS3
|Password|✓|OAS2,OAS3
|File|✓|OAS2
|Array|✓|OAS2,OAS3
|Maps|✓|ToolingExtension
|CollectionFormat|✓|OAS2
|CollectionFormatMulti|✓|OAS2
|Enum|✓|OAS2,OAS3
|ArrayOfEnum|✓|ToolingExtension
|ArrayOfModel|✓|ToolingExtension
|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
|ArrayOfCollectionOfModel|✓|ToolingExtension
|ArrayOfCollectionOfEnum|✓|ToolingExtension
|MapOfEnum|✓|ToolingExtension
|MapOfModel|✓|ToolingExtension
|MapOfCollectionOfPrimitives|✓|ToolingExtension
|MapOfCollectionOfModel|✓|ToolingExtension
|MapOfCollectionOfEnum|✓|ToolingExtension
### Documentation Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Readme|✗|ToolingExtension
|Model|✓|ToolingExtension
|Api|✓|ToolingExtension
### Global Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Host|✓|OAS2,OAS3
|BasePath|✓|OAS2,OAS3
|Info|✓|OAS2,OAS3
|Schemes|✗|OAS2,OAS3
|PartialSchemes|✓|OAS2,OAS3
|Consumes|✓|OAS2
|Produces|✓|OAS2
|ExternalDocumentation|✓|OAS2,OAS3
|Examples|✓|OAS2,OAS3
|XMLStructureDefinitions|✗|OAS2,OAS3
|MultiServer|✗|OAS3
|ParameterizedServer|✗|OAS3
|ParameterStyling|✗|OAS3
|Callbacks|✓|OAS3
|LinkObjects|✗|OAS3
### Parameter Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Path|✓|OAS2,OAS3
|Query|✓|OAS2,OAS3
|Header|✓|OAS2,OAS3
|Body|✓|OAS2
|FormUnencoded|✓|OAS2
|FormMultipart|✓|OAS2
|Cookie|✓|OAS3
### Schema Support Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Simple|✓|OAS2,OAS3
|Composite|✓|OAS2,OAS3
|Polymorphism|✓|OAS2,OAS3
|Union|✗|OAS3
### Security Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasicAuth|✓|OAS2,OAS3
|ApiKey|✓|OAS2,OAS3
|OpenIDConnect|✗|OAS3
|BearerToken|✓|OAS3
|OAuth2_Implicit|✓|OAS2,OAS3
|OAuth2_Password|✓|OAS2,OAS3
|OAuth2_ClientCredentials|✓|OAS2,OAS3
|OAuth2_AuthorizationCode|✓|OAS2,OAS3
### Wire Format Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|JSON|✓|OAS2,OAS3
|XML|✓|OAS2,OAS3
|PROTOBUF|✗|ToolingExtension
|Custom|✗|OAS2,OAS3

View File

@ -0,0 +1,185 @@
package org.openapitools.codegen.languages;
import org.openapitools.codegen.*;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PlantumlDocumentationCodegen extends DefaultCodegen implements CodegenConfig {
public static final String ALL_OF_SUFFIX = "AllOf";
static Logger LOGGER = LoggerFactory.getLogger(PlantumlDocumentationCodegen.class);
public CodegenType getTag() {
return CodegenType.DOCUMENTATION;
}
public String getName() {
return "plantuml";
}
public String getHelp() {
return "Generates a plantuml documentation.";
}
public PlantumlDocumentationCodegen() {
super();
outputFolder = "generated-code" + File.separator + "plantuml";
embeddedTemplateDir = templateDir = "plantuml-documentation";
supportingFiles.add(new SupportingFile("schemas.mustache", "", "schemas.plantuml"));
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
Object models = objs.get("models");
List<Object> modelsList = (List<Object>) models;
List<CodegenModel> codegenModelList = modelsList.stream()
.filter(listItem -> listItem instanceof HashMap<?, ?>)
.map(listItem -> (CodegenModel)((HashMap<?, ?>)listItem).get("model"))
.collect(Collectors.toList());
List<CodegenModel> inlineAllOfCodegenModelList = codegenModelList.stream()
.filter(codegenModel -> codegenModel.getClassname().endsWith(ALL_OF_SUFFIX))
.collect(Collectors.toList());
List<CodegenModel> nonInlineAllOfCodegenModelList = codegenModelList.stream()
.filter(codegenModel -> !codegenModel.getClassname().endsWith(ALL_OF_SUFFIX))
.collect(Collectors.toList());
List<CodegenModel> subtypeCodegenModelList = codegenModelList.stream()
.filter(codegenModel -> !codegenModel.allOf.isEmpty())
.collect(Collectors.toList());
List<Map<String, Object>> entities = calculateEntities(nonInlineAllOfCodegenModelList, inlineAllOfCodegenModelList);
objs.put("entities", entities);
List<Map<String, Object>> relationships = calculateCompositionRelationshipsFrom(entities);
objs.put("relationships", relationships);
List<Object> inheritances = calculateInheritanceRelationships(subtypeCodegenModelList);
objs.put("inheritances", inheritances);
return super.postProcessSupportingFileData(objs);
}
private List<Map<String, Object>> calculateEntities(List<CodegenModel> nonInlineAllOfCodegenModelList, List<CodegenModel> inlineAllOfCodegenModelList) {
Map<String, CodegenModel> inlineAllOfCodegenModelMap = inlineAllOfCodegenModelList.stream().collect(Collectors.toMap(cm -> cm.getClassname(), cm -> cm));
return nonInlineAllOfCodegenModelList.stream().map(codegenModel -> createEntityFor(codegenModel, inlineAllOfCodegenModelMap)).collect(Collectors.toList());
}
private Map<String, Object> createEntityFor(CodegenModel nonInlineAllOfCodegenModel, Map<String, CodegenModel> inlineAllOfCodegenModelMap) {
if (nonInlineAllOfCodegenModel.allOf.isEmpty()) {
return createEntityFor(nonInlineAllOfCodegenModel, nonInlineAllOfCodegenModel.getAllVars());
} else {
Optional<String> inheritingInlineAllOfName = nonInlineAllOfCodegenModel.allOf.stream().filter(allOfName -> allOfName.endsWith(ALL_OF_SUFFIX)).findFirst();
if (inheritingInlineAllOfName.isPresent()) {
if (inlineAllOfCodegenModelMap.containsKey(inheritingInlineAllOfName.get())) {
CodegenModel inheritingInlineAllOfModel = inlineAllOfCodegenModelMap.get(inheritingInlineAllOfName.get());
return createEntityFor(nonInlineAllOfCodegenModel, inheritingInlineAllOfModel.getAllVars());
}
}
return createEntityFor(nonInlineAllOfCodegenModel, Collections.emptyList());
}
}
private Map<String, Object> createEntityFor(CodegenModel codegenModel, List<CodegenProperty> properties) {
Map<String, Object> entity = new HashMap<>();
entity.put("name", removeSuffix(codegenModel.getClassname(), ALL_OF_SUFFIX));
List<Object> fields = properties.stream()
.map(var -> createFieldFor(var))
.collect(Collectors.toList());
entity.put("fields", fields);
return entity;
}
private Object createFieldFor(CodegenProperty codegenProperty) {
Map<String, Object> field = new HashMap<>();
field.put("name", codegenProperty.getBaseName());
field.put("isRequired", codegenProperty.getRequired());
field.put("isList", codegenProperty.isListContainer);
field.put("complexDataType", getComplexDataTypeFor(codegenProperty));
String dataType = codegenProperty.isListContainer && codegenProperty.getItems() != null ? "List<" + toModelName(codegenProperty.getItems().getDataType()) + ">" : toModelName(codegenProperty.getDataType());
field.put("dataType", dataType);
return field;
}
@SuppressWarnings("unchecked")
private List<Map<String, Object>> calculateCompositionRelationshipsFrom(List<Map<String, Object>> entities) {
Map<String, List<Map<String, Object>>> entityFieldsMap = entities.stream()
.collect(Collectors.toMap(entity -> (String)entity.get("name"), entity -> (List<Map<String, Object>>)entity.get("fields")));
return entityFieldsMap.entrySet().stream()
.map(entry -> createRelationshipsFor(entry.getKey(), entry.getValue()))
.flatMap(relationshipList -> relationshipList.stream())
.collect(Collectors.toList());
}
private List<Map<String, Object>> createRelationshipsFor(String entityName, List<Map<String, Object>> fields) {
return fields.stream()
.filter(field -> field.get("complexDataType") != null)
.map(complexField -> createRelationshipFor(complexField, entityName))
.collect(Collectors.toList());
}
private Map<String, Object> createRelationshipFor(Map<String, Object> complexField, String entityName) {
Map<String, Object> relationship = new HashMap<>();
relationship.put("parent", entityName);
relationship.put("child", complexField.get("complexDataType"));
relationship.put("name", complexField.get("name"));
relationship.put("isList", complexField.get("isList"));
return relationship;
}
private String getComplexDataTypeFor(CodegenProperty codegenProperty) {
if (codegenProperty.isModel) {
return toModelName(codegenProperty.getDataType());
} else if (codegenProperty.isListContainer && codegenProperty.getItems().isModel) {
return toModelName((codegenProperty.getItems().getDataType()));
}
return null;
}
private List<Object> calculateInheritanceRelationships(List<CodegenModel> subtypeCodegenModelList) {
return subtypeCodegenModelList.stream()
.map(subtypeModel -> getInterfacesForSubtype(subtypeModel))
.flatMap(subTypeInterfaces -> subTypeInterfaces.getValue().stream().map(parent -> createInheritance(parent, subTypeInterfaces.getKey())))
.collect(Collectors.toList());
}
private Map.Entry<String, List<String>> getInterfacesForSubtype(CodegenModel subtypeModel) {
List<String> interfaceList = subtypeModel.getInterfaces().stream()
.filter(inf -> !inf.endsWith(ALL_OF_SUFFIX))
.collect(Collectors.toList());
return new AbstractMap.SimpleEntry<>(subtypeModel.getClassname(), interfaceList);
}
private Object createInheritance(String parentName, String childName) {
Map<String, Object> inheritance = new HashMap<>();
inheritance.put("parent", parentName);
inheritance.put("child", childName);
return inheritance;
}
private String removeSuffix(final String value, final String suffix) {
if (value != null && suffix != null && value.endsWith(suffix)) {
return value.substring(0, value.length() - suffix.length());
}
return value;
}
}

View File

@ -130,3 +130,5 @@ org.openapitools.codegen.languages.MarkdownDocumentationCodegen
org.openapitools.codegen.languages.ScalaSttpClientCodegen
org.openapitools.codegen.languages.ScalaAkkaHttpServerCodegen
org.openapitools.codegen.languages.PlantumlDocumentationCodegen

View File

@ -0,0 +1,21 @@
@startuml
title {{appName}} Schemas Diagram
{{#entities}}
entity {{{name}}} {
{{#fields}}
{{#isRequired}}* {{/isRequired}}{{{name}}}: {{{dataType}}}
{{/fields}}
}
{{/entities}}
{{#inheritances}}
{{{parent}}} <|--- {{{child}}}
{{/inheritances}}
{{#relationships}}
{{{parent}}} -- {{#isList}}"0..*" {{/isList}}{{{child}}} : {{{name}}}
{{/relationships}}
@enduml

View File

@ -0,0 +1,307 @@
package org.openapitools.codegen.plantuml;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.languages.PlantumlDocumentationCodegen;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.*;
public class PlantumlDocumentationCodegenTest {
private PlantumlDocumentationCodegen plantumlDocumentationCodegen = new PlantumlDocumentationCodegen();
@Test
public void simpleEntityTest() {
final Schema model = new Schema()
.description("a sample model")
.addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT))
.addProperties("name", new StringSchema())
.addRequiredItem("id")
.addRequiredItem("name");
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
plantumlDocumentationCodegen.setOpenAPI(openAPI);
final CodegenModel cm = plantumlDocumentationCodegen.fromModel("sample", model);
Map<String, Object> objs = createObjectsMapFor(cm);
plantumlDocumentationCodegen.postProcessSupportingFileData(objs);
List<Object> entityList = getList(objs, "entities");
Assert.assertFalse(entityList.isEmpty(), "empty entity list");
Map<String, Object> firstEntity = getEntityFromList("Sample", entityList);
List<Object> fieldList = getList(firstEntity, "fields");
Assert.assertEquals(fieldList.size(), 2, "size of field list");
Map<String, Object> firstField = (Map<String, Object>)fieldList.get(0);
Assert.assertEquals((String)firstField.get("name"), "id");
Assert.assertTrue((boolean)firstField.get("isRequired"));
Assert.assertEquals((String)firstField.get("dataType"), "Long");
}
@Test
public void listFieldTest() {
final Schema model = new Schema()
.description("a sample model")
.addProperties("tags", new ArraySchema().items(new StringSchema()));
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
plantumlDocumentationCodegen.setOpenAPI(openAPI);
final CodegenModel cm = plantumlDocumentationCodegen.fromModel("sample", model);
Map<String, Object> objs = createObjectsMapFor(cm);
plantumlDocumentationCodegen.postProcessSupportingFileData(objs);
List<Object> entityList = getList(objs, "entities");
Map<String, Object> sampleEntity = getEntityFromList("Sample", entityList);
Map<String, Object> tagsField = getFieldFromEntity("tags", sampleEntity);
Assert.assertEquals((String)tagsField.get("dataType"), "List<String>");
}
@Test
public void inheritedEntitiesTest() {
OpenAPI openAPI = TestUtils.createOpenAPI();
final Schema parentSchema = new Schema()
.description("a parent model")
.addProperties("id", new StringSchema())
.addRequiredItem("id");
openAPI.getComponents().addSchemas("parent", parentSchema);
final Schema childAllOfInlineSchema = new Schema()
.description("an inline model")
.addProperties("name", new StringSchema());
openAPI.getComponents().addSchemas("child_allOf", childAllOfInlineSchema);
final ComposedSchema composedSchema = new ComposedSchema();
composedSchema.setDescription("a composed child model");
composedSchema.addAllOfItem(new Schema().$ref("#/components/schemas/parent"));
composedSchema.addAllOfItem(new Schema().$ref("#/components/schemas/child_allOf"));
openAPI.getComponents().addSchemas("child", composedSchema);
plantumlDocumentationCodegen.setOpenAPI(openAPI);
final CodegenModel parentModel = plantumlDocumentationCodegen.fromModel("parent", parentSchema);
final CodegenModel childAllOfModel = plantumlDocumentationCodegen.fromModel("child_allOf", childAllOfInlineSchema);
final CodegenModel childComposedModel = plantumlDocumentationCodegen.fromModel("child", composedSchema);
Map<String, Object> objs = createObjectsMapFor(parentModel, childComposedModel, childAllOfModel);
plantumlDocumentationCodegen.postProcessSupportingFileData(objs);
List<Object> entityList = getList(objs, "entities");
Assert.assertEquals(entityList.size(), 2, "size of entity list");
assertEntityDoesNotExistsInList("ChildAllOf", entityList);
Map<String, Object> parentEntity = getEntityFromList("Parent", entityList);
getFieldFromEntity("id", parentEntity);
Map<String, Object> childEntity = getEntityFromList("Child", entityList);
assertFieldDoesNotExistsInEntity("id", childEntity);
getFieldFromEntity("name", childEntity);
List<Object> inheritanceList = getList(objs, "inheritances");
Assert.assertEquals(inheritanceList.size(), 1, "size of inheritance list");
Map<String, String> firstInheritance = (Map<String, String>)inheritanceList.get(0);
Assert.assertEquals(firstInheritance.get("parent"), "Parent");
Assert.assertEquals(firstInheritance.get("child"), "Child");
}
@Test
public void aggregatedEntitiesTest() {
OpenAPI openAPI = TestUtils.createOpenAPI();
final Schema simpleDataTypeSchema = new Schema()
.description("a simple model")
.addProperties("name", new StringSchema());
openAPI.getComponents().addSchemas("simple", simpleDataTypeSchema);
final Schema tagDataTypeSchema = new Schema()
.description("a tag model")
.addProperties("name", new StringSchema());
openAPI.getComponents().addSchemas("tag", tagDataTypeSchema);
final Schema parentSchema = new Schema()
.description("a parent model")
.addProperties("id", new StringSchema())
.addProperties("name", new Schema().$ref("#/components/schemas/simple"))
.addProperties("tags", new ArraySchema().items(new Schema().$ref("#/components/schemas/tag")))
.addRequiredItem("id");
openAPI.getComponents().addSchemas("parent", parentSchema);
plantumlDocumentationCodegen.setOpenAPI(openAPI);
final CodegenModel simpleModel = plantumlDocumentationCodegen.fromModel("simple", simpleDataTypeSchema);
final CodegenModel tagModel = plantumlDocumentationCodegen.fromModel("tag", tagDataTypeSchema);
final CodegenModel parentModel = plantumlDocumentationCodegen.fromModel("parent", parentSchema);
Map<String, Object> objs = createObjectsMapFor(parentModel, simpleModel, tagModel);
plantumlDocumentationCodegen.postProcessSupportingFileData(objs);
List<Object> entityList = getList(objs, "entities");
Assert.assertEquals(entityList.size(), 3, "size of entity list");
Map<String, Object> parentEntity = getEntityFromList("Parent", entityList);
Map<String, Object> nameField = getFieldFromEntity("name", parentEntity);
Assert.assertEquals((String)nameField.get("dataType"), "Simple");
Map<String, Object> tagsField = getFieldFromEntity("tags", parentEntity);
Assert.assertEquals((String)tagsField.get("dataType"), "List<Tag>");
List<Object> relationshipList = getList(objs, "relationships");
Assert.assertEquals(relationshipList.size(), 2, "size of relationship list");
Map<String, Object> firstRelationship = (Map<String, Object>)relationshipList.get(0);
Assert.assertEquals((String)firstRelationship.get("parent"), "Parent");
Assert.assertEquals((String)firstRelationship.get("child"), "Simple");
Assert.assertEquals((String)firstRelationship.get("name"), "name");
Assert.assertFalse((boolean)firstRelationship.get("isList"));
Map<String, Object> secondRelationship = (Map<String, Object>)relationshipList.get(1);
Assert.assertEquals((String)secondRelationship.get("parent"), "Parent");
Assert.assertEquals((String)secondRelationship.get("child"), "Tag");
Assert.assertEquals((String)secondRelationship.get("name"), "tags");
Assert.assertTrue((boolean)secondRelationship.get("isList"));
}
@Test
public void sharedIdenticalInlineAllOfTest() {
OpenAPI openAPI = TestUtils.createOpenAPI();
final Schema parentSchema = new Schema()
.description("a parent model")
.addProperties("id", new StringSchema());
openAPI.getComponents().addSchemas("parent", parentSchema);
final Schema tagDataTypeSchema = new Schema()
.description("a tag model")
.addProperties("name", new StringSchema());
openAPI.getComponents().addSchemas("tag", tagDataTypeSchema);
final Schema anotherAllOfInlineSchema = new Schema()
.description("an identical inline model used elsewhere")
.addProperties("tag", new Schema().$ref("#/components/schemas/tag"));
openAPI.getComponents().addSchemas("another_allOf", anotherAllOfInlineSchema);
final ComposedSchema composedSchema = new ComposedSchema();
composedSchema.setDescription("a composed child model");
composedSchema.addAllOfItem(new Schema().$ref("#/components/schemas/parent"));
composedSchema.addAllOfItem(new Schema().$ref("#/components/schemas/another_allOf"));
openAPI.getComponents().addSchemas("child", composedSchema);
plantumlDocumentationCodegen.setOpenAPI(openAPI);
final CodegenModel parentModel = plantumlDocumentationCodegen.fromModel("parent", parentSchema);
final CodegenModel childAllOfModel = plantumlDocumentationCodegen.fromModel("another_allOf", anotherAllOfInlineSchema);
final CodegenModel childComposedModel = plantumlDocumentationCodegen.fromModel("child", composedSchema);
Map<String, Object> objs = createObjectsMapFor(parentModel, childComposedModel, childAllOfModel);
plantumlDocumentationCodegen.postProcessSupportingFileData(objs);
List<Object> entityList = getList(objs, "entities");
Assert.assertEquals(entityList.size(), 2, "size of entity list");
Map<String, Object> parentEntity = getEntityFromList("Parent", entityList);
Map<String, Object> childEntity = getEntityFromList("Child", entityList);
List<Object> inheritanceList = getList(objs, "inheritances");
Assert.assertEquals(inheritanceList.size(), 1, "size of inheritance list");
Map<String, String> firstInheritance = (Map<String, String>)inheritanceList.get(0);
Assert.assertEquals(firstInheritance.get("parent"), "Parent");
Assert.assertEquals(firstInheritance.get("child"), "Child");
List<Object> relationshipList = getList(objs, "relationships");
Assert.assertEquals(relationshipList.size(), 1, "size of relationship list");
Map<String, Object> firstRelationship = (Map<String, Object>)relationshipList.get(0);
Assert.assertEquals((String)firstRelationship.get("parent"), "Child");
Assert.assertEquals((String)firstRelationship.get("child"), "Tag");
}
private Map<String, Object> createObjectsMapFor(CodegenModel... codegenModels) {
List<Map<?,?>> modelsList = new ArrayList();
for (CodegenModel codegenModel: codegenModels) {
Map<String, Object> modelMap = new HashMap<>();
modelMap.put("model", codegenModel);
modelsList.add(modelMap);
}
Map<String, Object> objs = new HashMap<>();
objs.put("models", modelsList);
return objs;
}
private Map<String, Object> toMap(Object entityItem) {
return (Map<String, Object>)entityItem;
}
private boolean hasName(String name, Map<String, Object> map) {
return (map.get("name")).equals(name);
}
private void assertEntityDoesNotExistsInList(String name, List<?> entityList) {
long count = entityList.stream()
.map(entityItem -> toMap(entityItem))
.filter(entityMap -> hasName(name, entityMap))
.count();
Assert.assertEquals(count, 0, "entries with name " + name);
}
private Map<String, Object> getEntityFromList(String name, List<?> entityList) {
Optional<Map<String, Object>> entity = entityList.stream()
.map(entityItem -> toMap(entityItem))
.filter(entityMap -> hasName(name, entityMap))
.findFirst();
Assert.assertTrue(entity.isPresent(), "entity with name '" + name + "' found in list");
return entity.get();
}
private void assertFieldDoesNotExistsInEntity(String name, Map<String, Object> entity) {
List<Object> fieldList = (List<Object>)entity.get("fields");
long count = fieldList.stream()
.map(fieldItem -> toMap(fieldItem))
.filter(fieldMap -> hasName(name, fieldMap))
.count();
Assert.assertEquals(count, 0, "fields with name " + name);
}
private Map<String, Object> getFieldFromEntity(String name, Map<String, Object> entity) {
List<Object> fieldList = (List<Object>)entity.get("fields");
Optional<Map<String, Object>> field = fieldList.stream()
.map(fieldItem -> toMap(fieldItem))
.filter(fieldMap -> hasName(name, fieldMap))
.findFirst();
Assert.assertTrue(field.isPresent(), "field with name '" + name + "' found in list");
return field.get();
}
private List<Object> getList(Map<String, Object> objs, String listName) {
Object list = objs.get(listName);
Assert.assertNotNull(list, "object with name '" + listName + "' in objs map");
Assert.assertTrue(list instanceof List<?>, "object with name '" + listName + "' in objs map is a list");
return (List<Object>)list;
}
}

View File

@ -0,0 +1,25 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
#*.plantuml

View File

@ -0,0 +1 @@
5.0.0-SNAPSHOT

View File

@ -0,0 +1,54 @@
@startuml
title OpenAPI Petstore Schemas Diagram
entity ApiResponse {
code: Integer
type: String
message: String
}
entity Category {
id: Long
name: String
}
entity Order {
id: Long
petId: Long
quantity: Integer
shipDate: Date
status: String
complete: Boolean
}
entity Pet {
id: Long
category: Category
* name: String
* photoUrls: List<String>
tags: List<Tag>
status: String
}
entity Tag {
id: Long
name: String
}
entity User {
id: Long
username: String
firstName: String
lastName: String
email: String
password: String
phone: String
userStatus: Integer
}
Pet -- Category : category
Pet -- "0..*" Tag : tags
@enduml