Skip to content

Commit

Permalink
312 bug duplicate name object result in incorrect code generation (#329)
Browse files Browse the repository at this point in the history
* multiapi-312: Fix asyncApi duplicated name in different package error.
  • Loading branch information
rafaelgonzalezsimon authored Aug 4, 2024
1 parent f1c42c5 commit 2f20b8d
Show file tree
Hide file tree
Showing 22 changed files with 822 additions and 61 deletions.
67 changes: 58 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ As commented above, they both could be used at the same time, setting a double
<plugin>
<groupId>com.sngular</groupId>
<artifactId>scs-multiapi-maven-plugin</artifactId>
<version>5.4.2</version>
<version>5.4.3</version>
<executions>
<execution>
<id>asyncapi</id>
Expand Down Expand Up @@ -104,6 +104,32 @@ In the [AsyncApi Generator](#asyncapi-generator) and the
[OpenApi Generator](#openapi-generator) sections, you can find more information
about how they work, and the parameters and configuration options they offer.

#### Mandatory dependencies

These dependencies are used by generated code

```xml
<dependencies>
<dependency>
<groupId>io.swagger.parser.v3</groupId>
<artifactId>swagger-parser-core</artifactId>
<version>2.1.20</version>
</dependency>

<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<version>2.2.20</version>
</dependency>

<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
```

### How to configure the build file

To maintain the generation of the different types of classes independent, they
Expand All @@ -114,7 +140,7 @@ Apply the plugin in the `build.gradle` file and invoke the task.
```groovy
plugins {
id "java"
id "com.sngular.scs-multiapi-gradle-plugin' version '5.4.2"
id "com.sngular.scs-multiapi-gradle-plugin' version '5.4.3"
openapimodel {
Expand All @@ -138,6 +164,16 @@ In the [AsyncApi Generator](#asyncapi-generator) and the
[OpenApi Generator](#openapi-generator) sections, you can find more information
about how they work, and the parameters and configuration options they offer.

#### Mandatory gradle dependencies

These dependencies are used by generated code

``` gradle
implementation 'io.swagger.parser.v3:swagger-parser-core:2.1.20'
implementation 'io.swagger.core.v3:swagger-annotations-jakarta:2.2.20'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
```

## AsyncApi Generator

### Configuration
Expand All @@ -153,7 +189,7 @@ which the plugin is designed.
<plugin>
<groupId>com.sngular</groupId>
<artifactId>scs-multiapi-maven-plugin</artifactId>
<version>5.4.2</version>
<version>5.4.3</version>
<executions>
<execution>
<phase>generate-sources</phase>
Expand Down Expand Up @@ -443,8 +479,16 @@ public interface ISubscribeOperation {

#### Bindings

Asyncapi support a way to specify specific configuration for certain protocols. Nowadays we only support Kafka specific information to define a Key form Messages as you can find [here](<https://github.com/asyncapi/bindings/blob/master/kafka/README.md>).
When a binding is specified in a message we will generate a generic class named as MessageWrapper which will contain the payload and the key used in to build a Message.
Asyncapi support a way to specify specific configuration for certain protocols.

Nowadays, we only support Kafka specific information to define a Key form.

Messages as you can find
[here](<https://github.com/asyncapi/bindings/blob/master/kafka/README.md>).

When a binding is specified in a message we will generate a generic class
named as MessageWrapper which will contain the payload and the key
used in to build a Message.
You will find such class by each api package you define.

##### Mapper
Expand Down Expand Up @@ -584,7 +628,7 @@ file. Here is an example of a basic configuration:
<plugin>
<groupId>com.sngular</groupId>
<artifactId>scs-multiapi-maven-plugin</artifactId>
<version>5.4.2</version>
<version>5.4.3</version>
<executions>
<execution>
<goals>
Expand Down Expand Up @@ -664,9 +708,14 @@ openapimodel {
specFile {
{
filePath = './src/main/resources/api/api.yml'
apiPackage = 'com.sngular.apigenerator.openapi.api'
modelPackage = 'com.sngular.apigenerator.openapi.api.model'
useTagsGroup = true
consumer {
apiPackage = 'com.sngular.apigenerator.openapi.api'
modelPackage = 'com.sngular.apigenerator.openapi.api.model'
}
supplier {
apiPackage = 'com.sngular.apigenerator.openapi.api'
modelPackage = 'com.sngular.apigenerator.openapi.api.model'
}
}
overWriteModel = true
}
Expand Down
4 changes: 2 additions & 2 deletions multiapi-engine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.sngular</groupId>
<artifactId>multiapi-engine</artifactId>
<version>5.4.2</version>
<version>5.4.3</version>
<packaging>jar</packaging>

<properties>
Expand All @@ -20,7 +20,7 @@
<junit-jupiter-engine.version>5.9.2</junit-jupiter-engine.version>
<swagger-core.version>2.2.9</swagger-core.version>
<maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public final void processFileSpec(final List<SpecFile> specsListFile) {
final ObjectMapper om = new ObjectMapper(new YAMLFactory());
processedOperationIds.clear();
generateExceptionTemplate = false;
for (SpecFile fileParameter : specsListFile) {
for (final SpecFile fileParameter : specsListFile) {
final Pair<InputStream, FileLocation> ymlFileAndPath;
try {
ymlFileAndPath = resolveYmlLocation(fileParameter.getFilePath());
Expand Down Expand Up @@ -471,7 +471,7 @@ private void fillTemplateFactory(
final JsonNode schemaToBuild = processedMethod.getPayload();
if (shouldBuild(schemaToBuild)) {
final var schemaObjectIt =
MapperContentUtil.mapComponentToSchemaObject(totalSchemas, className, schemaToBuild, null, operationObject.getModelNameSuffix(), parentPackage,
MapperContentUtil.mapComponentToSchemaObject(totalSchemas, className, schemaToBuild, null, operationObject.getModelNameSuffix(), parentPackage, modelPackage,
operationObject.getFormats(), operationObject.getUseTimeType()).iterator();

if (schemaObjectIt.hasNext()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

package com.sngular.api.generator.plugin.asyncapi.util;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -64,19 +66,19 @@ private MapperContentUtil() {}
public static List<SchemaObject> mapComponentToSchemaObject(
final Map<String, JsonNode> totalSchemas, final String component, final JsonNode model,
final String prefix, final String suffix, final String parentPackage,
final Map<String, String> formats, final TimeType useTimeType) {
final String modelPackage, final Map<String, String> formats, final TimeType useTimeType) {
final List<SchemaObject> schemasList = new ArrayList<>();
if (Objects.nonNull(model)) {
final Queue<String> modelToBuildList = new ConcurrentLinkedQueue<>();
final List<String> alreadyBuilt = new ArrayList<>();
schemasList.add(buildSchemaObject(totalSchemas, component, model, prefix, suffix, modelToBuildList, parentPackage, formats, useTimeType));
schemasList.add(buildSchemaObject(totalSchemas, component, model, prefix, suffix, modelToBuildList, parentPackage, modelPackage, formats, useTimeType));
while (!modelToBuildList.isEmpty()) {

final var modelToBuild = modelToBuildList.remove();
if (!alreadyBuilt.contains(modelToBuild)) {
final var path = MapperUtil.splitName(modelToBuild);
final var nexElement = buildSchemaObject(totalSchemas, modelToBuild, totalSchemas.get(getComponent(path)),
prefix, suffix, modelToBuildList, getParentName(path), formats, useTimeType);
prefix, suffix, modelToBuildList, getParentName(path), modelPackage, formats, useTimeType);
if (schemasList.contains(nexElement)) {
modelToBuildList.poll();
} else {
Expand All @@ -92,14 +94,24 @@ public static List<SchemaObject> mapComponentToSchemaObject(
private static String getParentName(final String[] path) {
final String parenName;
if (path.length > 1) {
parenName = path[path.length - 2];
parenName = String.join(".", Arrays.copyOf(path, path.length - 1));
} else {
parenName = "";
}
return parenName;
}

private static String getComponent(final String[] path) {
final String componentName;
if (path.length > 1) {
componentName = String.join(".", Arrays.copyOf(path, path.length - 1)) + "/" + path[path.length - 1];
} else {
componentName = path[0];
}
return componentName.toUpperCase();
}

private static String getSchema(final String[] path) {
final String componentName;
if (path.length > 1) {
componentName = path[path.length - 2] + "/" + path[path.length - 1];
Expand All @@ -112,9 +124,9 @@ private static String getComponent(final String[] path) {
private static SchemaObject buildSchemaObject(
final Map<String, JsonNode> totalSchemas, final String component, final JsonNode model,
final String prefix, final String suffix, final Collection<String> modelToBuildList, final String parentPackage,
final Map<String, String> formats, final TimeType useTimeType) {
final String modelPackage, final Map<String, String> formats, final TimeType useTimeType) {

final var listSchema = getFields(totalSchemas, model, true, prefix, suffix, modelToBuildList, parentPackage, formats, useTimeType);
final var listSchema = getFields(totalSchemas, model, true, prefix, suffix, modelToBuildList, modelPackage, formats, useTimeType);
final var splitPackage = MapperUtil.splitName(component);
final String className = splitPackage[splitPackage.length - 1];
return SchemaObject.builder()
Expand All @@ -130,7 +142,7 @@ private static SchemaObject buildSchemaObject(
private static List<String> getImportList(final List<SchemaFieldObject> schemaListToImport) {
final var importList = new HashSet<String>();

for (SchemaFieldObject fieldObject : schemaListToImport) {
for (final SchemaFieldObject fieldObject : schemaListToImport) {
importList.addAll(getTypeImports(fieldObject));
}
return new ArrayList<>(importList);
Expand Down Expand Up @@ -168,31 +180,31 @@ private static List<String> getTypeImports(final SchemaFieldObject fieldObject)

private static List<SchemaFieldObject> getFields(
final Map<String, JsonNode> totalSchemas, final JsonNode model, final boolean required, final String prefix,
final String suffix, final Collection<String> modelToBuildList, final String parentPackage,
final Map<String, String> formats, final TimeType useTimeType) {
final String suffix, final Collection<String> modelToBuildList,
final String modelPackage, final Map<String, String> formats, final TimeType useTimeType) {
final var fieldObjectArrayList = new ArrayList<SchemaFieldObject>();
schemaCombinatorType = null;
if (ApiTool.hasType(model)) {
if (OBJECT.equalsIgnoreCase(model.get(TYPE).textValue())) {
fieldObjectArrayList.addAll(processFieldObject(totalSchemas, model, prefix, suffix, modelToBuildList, parentPackage, formats, useTimeType));
fieldObjectArrayList.addAll(processFieldObject(totalSchemas, model, prefix, suffix, modelToBuildList, modelPackage, formats, useTimeType));
} else if (ARRAY.equalsIgnoreCase(model.get(TYPE).textValue())) {
fieldObjectArrayList.add(processFieldObjectList(totalSchemas, "", model, required, prefix, suffix, modelToBuildList, parentPackage, null, formats, useTimeType));
fieldObjectArrayList.add(processFieldObjectList(totalSchemas, "", model, required, prefix, suffix, modelToBuildList, modelPackage, null, formats, useTimeType));
} else if ("enum".equalsIgnoreCase(model.get(TYPE).textValue())) {
fieldObjectArrayList.add(processFieldObjectList(totalSchemas, "", model, required, prefix, suffix, modelToBuildList, parentPackage, null, formats, useTimeType));
fieldObjectArrayList.add(processFieldObjectList(totalSchemas, "", model, required, prefix, suffix, modelToBuildList, modelPackage, null, formats, useTimeType));
}
} else if (ApiTool.hasRef(model)) {
final var splitName = MapperUtil.splitName(ApiTool.getRefValue(model));
fieldObjectArrayList.addAll(processFieldObject(totalSchemas, totalSchemas.get(MapperUtil.buildKey(splitName)), prefix, suffix,
modelToBuildList, parentPackage, formats, useTimeType));
modelToBuildList, modelPackage, formats, useTimeType));
} else if (model.elements().hasNext()) {
fieldObjectArrayList.addAll(processFieldObject(totalSchemas, model, prefix, suffix, modelToBuildList, parentPackage, formats, useTimeType));
fieldObjectArrayList.addAll(processFieldObject(totalSchemas, model, prefix, suffix, modelToBuildList, modelPackage, formats, useTimeType));
}
return fieldObjectArrayList;
}

private static List<SchemaFieldObject> processFieldObject(
final Map<String, JsonNode> totalSchemas, final JsonNode model, final String prefix, final String suffix, final Collection<String> modelToBuildList,
final String parentPackage, final Map<String, String> formats, final TimeType useTimeType) {
final String modelPackage, final Map<String, String> formats, final TimeType useTimeType) {
final Set<String> requiredSet = new HashSet<>();
final var fieldObjectArrayList = new ArrayList<SchemaFieldObject>();
if (model.has("required")) {
Expand All @@ -206,8 +218,8 @@ private static List<SchemaFieldObject> processFieldObject(
while (propertiesIt.hasNext()) {
final var property = propertiesIt.next();
fieldObjectArrayList.add(processFieldObjectList(totalSchemas, property, model.get(PROPERTIES).path(property), requiredSet.contains(property), prefix, suffix,
modelToBuildList, parentPackage, null, formats, useTimeType));
if (model.get(PROPERTIES).path(property).has(REF) && !totalSchemas.containsKey(createKey(parentPackage, property.toUpperCase(), "/"))) {
modelToBuildList, modelPackage, null, formats, useTimeType));
if (model.get(PROPERTIES).path(property).has(REF) && !totalSchemas.containsKey(createKey(modelPackage, property.toUpperCase(), "/"))) {
modelToBuildList.add(MapperUtil.getLongRefClass(model.get(PROPERTIES).path(property)));
}
}
Expand Down Expand Up @@ -308,7 +320,7 @@ private static SchemaFieldObject processFieldObjectList(
}
} else if (ApiTool.hasRef(schema)) {
final var splitName = MapperUtil.splitName(ApiTool.getRefValue(schema));
final var solvedRef = totalSchemas.get(getComponent(splitName));
final var solvedRef = totalSchemas.get(getSchema(splitName));
fieldObject = processFieldObjectList(totalSchemas, name, solvedRef, required, prefix, suffix, modelToBuildList, modelPackage,
splitName[splitName.length - 1], formats, useTimeType);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ private static List<String> getSecurityRequirementList(final JsonNode securityNo

public static List<PathObject> mapPathObjects(final SpecFile specFile, final Collection<Map<String, JsonNode>> path, final GlobalObject globalObject, final Path baseDir) {
final List<PathObject> pathObjects = new ArrayList<>();
for (var pathMap : path) {
for (var pathItem : pathMap.entrySet()) {
for (final var pathMap : path) {
for (final var pathItem : pathMap.entrySet()) {
final PathObject pathObject = PathObject.builder()
.pathName(pathItem.getKey())
.globalObject(globalObject)
Expand All @@ -85,7 +85,7 @@ private static List<OperationObject> mapOperationObject(final SpecFile specFile,
final List<String> operationIdList = new ArrayList<>();
final var pathNode = path.getValue();
final var pathParameters = new ArrayList<ParameterObject>();
for (Iterator<Entry<String, JsonNode>> it = pathNode.fields(); it.hasNext();) {
for (final Iterator<Entry<String, JsonNode>> it = pathNode.fields(); it.hasNext();) {
final var field = it.next();
switch (field.getKey()) {
case "get":
Expand Down Expand Up @@ -218,7 +218,7 @@ private static List<ParameterObject> mapParameterObjects(
final GlobalObject globalObject, final Path baseDir) {
final List<ParameterObject> parameterObjects = new ArrayList<>();
if (Objects.nonNull(parameters) && !parameters.isEmpty()) {
for (JsonNode parameter : parameters) {
for (final JsonNode parameter : parameters) {
if (ApiTool.hasRef(parameter)) {
final JsonNode refParameter = globalObject.getParameterNode(MapperUtil.getRefSchemaName(parameter)).orElseThrow();
parameterObjects.add(buildParameterObject(specFile, globalObject, refParameter, baseDir));
Expand Down Expand Up @@ -260,7 +260,7 @@ private static List<ParameterObject> buildParameterContent(
final var content = ApiTool.getNode(parameter, CONTENT);
final var parameterName = ApiTool.getName(parameter);
final var parameterObjects = new ArrayList<ParameterObject>();
for (Iterator<JsonNode> it = content.elements(); it.hasNext();) {
for (final Iterator<JsonNode> it = content.elements(); it.hasNext();) {
final var contentType = it.next();
final String inlineParameter = INLINE_PARAMETER + safeCapitalize(contentClassName)
+ StringUtils.capitalize(parameterName);
Expand Down Expand Up @@ -337,7 +337,7 @@ private static List<ContentObject> mapContentObject(
final Path baseDir) {
final List<ContentObject> contentObjects = new ArrayList<>();
if (Objects.nonNull(content)) {
for (Iterator<String> it = content.fieldNames(); it.hasNext(); ) {
for (final Iterator<String> it = content.fieldNames(); it.hasNext();) {
final String mediaType = it.next();
final var schema = ApiTool.getNode(ApiTool.getNode(content, mediaType), SCHEMA);
final String pojoName = preparePojoName(inlineObject, schema, specFile);
Expand Down
Loading

0 comments on commit 2f20b8d

Please sign in to comment.