forked from loafle/openapi-generator-original
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:
parent
9f95f0cf3d
commit
f03458dde4
31
bin/openapi3/plantuml-documentation-petstore.sh
Normal file
31
bin/openapi3/plantuml-documentation-petstore.sh
Normal 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}
|
10
bin/openapi3/windows/plantuml-documentation-petstore.bat
Normal file
10
bin/openapi3/windows/plantuml-documentation-petstore.bat
Normal 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%
|
31
bin/plantuml-documentation-petstore.sh
Normal file
31
bin/plantuml-documentation-petstore.sh
Normal 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}
|
10
bin/windows/plantuml-documentation-petstore.bat
Normal file
10
bin/windows/plantuml-documentation-petstore.bat
Normal 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%
|
@ -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
159
docs/generators/plantuml.md
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
@ -130,3 +130,5 @@ org.openapitools.codegen.languages.MarkdownDocumentationCodegen
|
||||
org.openapitools.codegen.languages.ScalaSttpClientCodegen
|
||||
|
||||
org.openapitools.codegen.languages.ScalaAkkaHttpServerCodegen
|
||||
|
||||
org.openapitools.codegen.languages.PlantumlDocumentationCodegen
|
||||
|
21
modules/openapi-generator/src/main/resources/plantuml-documentation/schemas.mustache
vendored
Normal file
21
modules/openapi-generator/src/main/resources/plantuml-documentation/schemas.mustache
vendored
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -0,0 +1 @@
|
||||
5.0.0-SNAPSHOT
|
54
samples/documentation/petstore/plantuml/schemas.plantuml
Normal file
54
samples/documentation/petstore/plantuml/schemas.plantuml
Normal 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
|
Loading…
x
Reference in New Issue
Block a user