Skip to content

Commit

Permalink
Auto solve problem schema names conflicts for classes with same name,…
Browse files Browse the repository at this point in the history
… but with different packages.

Fixed micronaut-projects#947
  • Loading branch information
altro3 committed Mar 8, 2023
1 parent e0de343 commit f5bbf85
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ abstract class AbstractOpenApiVisitor {
private static final Schema<?> EMPTY_SCHEMA = new Schema<>();
private static final ComposedSchema EMPTY_COMPOSED_SCHEMA = new ComposedSchema();

/**
* Stores relations between schema names and class names.
*/
private final Map<String, String> schemaNameToClassNameMap = new HashMap<>();
/**
* Stores class name counters for schema suffix, when found classes with same name in different packages.
*/
private final Map<String, Integer> shemaNameSuffixCounterMap = new HashMap<>();
/**
* Stores the current in progress type.
*/
Expand Down Expand Up @@ -2175,28 +2183,41 @@ private String computeDefaultSchemaName(Element definingElement, Element type, M
return NameUtils.getSimpleName(metaAnnName);
}
String packageName;
String javaName;
String resultSchemaName;
if (type instanceof TypedElement && !(type instanceof EnumElement)) {
ClassElement typeType = ((TypedElement) type).getType();
packageName = typeType.getPackageName();
if (CollectionUtils.isNotEmpty(typeType.getTypeArguments())) {
javaName = computeNameWithGenerics(typeType, typeArgs, context);
resultSchemaName = computeNameWithGenerics(typeType, typeArgs, context);
} else {
javaName = computeNameWithGenerics(typeType, Collections.emptyMap(), context);
resultSchemaName = computeNameWithGenerics(typeType, Collections.emptyMap(), context);
}
} else {
javaName = type.getSimpleName();
resultSchemaName = type.getSimpleName();
packageName = NameUtils.getPackageName(type.getName());
}

OpenApiApplicationVisitor.SchemaDecorator schemaDecorator = OpenApiApplicationVisitor.getSchemaDecoration(packageName, context);
javaName = javaName.replace("$", ".");
resultSchemaName = resultSchemaName.replace("$", ".");
if (schemaDecorator != null) {
javaName = (StringUtils.hasText(schemaDecorator.getPrefix()) ? schemaDecorator.getPrefix() : StringUtils.EMPTY_STRING)
+ javaName
resultSchemaName = (StringUtils.hasText(schemaDecorator.getPrefix()) ? schemaDecorator.getPrefix() : StringUtils.EMPTY_STRING)
+ resultSchemaName
+ (StringUtils.hasText(schemaDecorator.getPostfix()) ? schemaDecorator.getPostfix() : StringUtils.EMPTY_STRING);
}
String fullClassNameWithGenerics = packageName + '.' + resultSchemaName;

// Check if the class exists in other packages. If so, you need to add a suffix,
// because there are two classes in different packages, but with the same class name.
String storedClassName = schemaNameToClassNameMap.get(resultSchemaName);
if (storedClassName != null && !storedClassName.equals(fullClassNameWithGenerics)) {
int index = shemaNameSuffixCounterMap.getOrDefault(resultSchemaName, 0);
index++;
shemaNameSuffixCounterMap.put(resultSchemaName, index);
resultSchemaName += "_" + index;
}
schemaNameToClassNameMap.put(resultSchemaName, fullClassNameWithGenerics);

return javaName;
return resultSchemaName;
}

private String computeNameWithGenerics(ClassElement classElement, Map<String, ClassElement> typeArgs, VisitorContext context) {
Expand Down
17 changes: 17 additions & 0 deletions openapi/src/test/groovy/io/micronaut/openapi/test1/Entity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.openapi.test1;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Entity {

private final String fieldB;

public Entity(final String fieldB) {
this.fieldB = fieldB;
}

public String getFieldB() {
return fieldB;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.openapi.test1;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class EntityWithGeneric<T> {

private final T fieldB;

public EntityWithGeneric(final T fieldB) {
this.fieldB = fieldB;
}

public T getFieldB() {
return fieldB;
}
}
17 changes: 17 additions & 0 deletions openapi/src/test/groovy/io/micronaut/openapi/test2/Entity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.openapi.test2;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Entity {

private final String fieldA;

public Entity(final String fieldA) {
this.fieldA = fieldA;
}

public String getFieldA() {
return fieldA;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.openapi.test2;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class EntityWithGeneric<T, R> {

private final T fieldA;

public EntityWithGeneric(final T fieldA) {
this.fieldA = fieldA;
}

public T getFieldA() {
return fieldA;
}
}
17 changes: 17 additions & 0 deletions openapi/src/test/groovy/io/micronaut/openapi/test3/Entity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.openapi.test3;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Entity {

private final String fieldC;

public Entity(final String fieldC) {
this.fieldC = fieldC;
}

public String getFieldC() {
return fieldC;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.micronaut.openapi.test3;

import io.micronaut.core.annotation.Introspected;

@Introspected
public class EntityWithGeneric<T, R> {

private final T fieldC;

public EntityWithGeneric(final T fieldC) {
this.fieldC = fieldC;
}

public T getFieldC() {
return fieldC;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1436,4 +1436,143 @@ public class MyBean {}
schema.properties.mySubObject.type == null
schema.properties.mySubObject.format == null
}

@Issue("https://github.com/micronaut-projects/micronaut-openapi/issues/947")
void "test dto schema with same name class in different packages"() {
when:
buildBeanDefinition("test.MyBean", '''
package test;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.openapi.test1.Entity;
@Controller
class TestController {
@Post("test1")
String test1(@Body Entity entity) {
return null;
}
@Post("test2")
String test2(@Body io.micronaut.openapi.test2.Entity entity) {
return null;
}
@Post("test3")
String test3(@Body io.micronaut.openapi.test3.Entity entity) {
return null;
}
}
@jakarta.inject.Singleton
public class MyBean {}
''')

OpenAPI openAPI = Utils.testReference

then:
openAPI.components.schemas
openAPI.components.schemas.size() == 3
Schema entityTest1 = openAPI.components.schemas.Entity
Schema entityTest2 = openAPI.components.schemas.Entity_1
Schema entityTest3 = openAPI.components.schemas.Entity_2

entityTest1
entityTest1.properties.fieldB
entityTest1.properties.fieldB.type == 'string'

entityTest2
entityTest2.properties.fieldA
entityTest2.properties.fieldA.type == 'string'

entityTest3
entityTest3.properties.fieldC
entityTest3.properties.fieldC.type == 'string'
}

void "test dto schema with same name class in different packages with generics"() {
when:
buildBeanDefinition("test.MyBean", '''
package test;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.openapi.test1.EntityWithGeneric;
@Controller
class TestController {
@Post("test1")
String test1(@Body EntityWithGeneric<String> entity) {
return null;
}
@Post("test11")
String test11(@Body EntityWithGeneric<Integer> entity) {
return null;
}
@Post("test2")
String test2(@Body io.micronaut.openapi.test2.EntityWithGeneric<String, Integer> entity) {
return null;
}
@Post("test22")
String test22(@Body io.micronaut.openapi.test2.EntityWithGeneric<String, String> entity) {
return null;
}
@Post("test3")
String test3(@Body io.micronaut.openapi.test3.EntityWithGeneric<String, Integer> entity) {
return null;
}
@Post("test33")
String test33(@Body io.micronaut.openapi.test3.EntityWithGeneric<String, String> entity) {
return null;
}
}
@jakarta.inject.Singleton
public class MyBean {}
''')

OpenAPI openAPI = Utils.testReference

then:
openAPI.components.schemas
openAPI.components.schemas.size() == 6
Schema entityTest1 = openAPI.components.schemas.EntityWithGeneric_String_
Schema entityTest11 = openAPI.components.schemas.EntityWithGeneric_Integer_
Schema entityTest2 = openAPI.components.schemas."EntityWithGeneric_String.Integer_"
Schema entityTest22 = openAPI.components.schemas."EntityWithGeneric_String.String_"
Schema entityTest3 = openAPI.components.schemas."EntityWithGeneric_String.Integer__1"
Schema entityTest33 = openAPI.components.schemas."EntityWithGeneric_String.String__1"

entityTest1
entityTest1.properties.fieldB
entityTest1.properties.fieldB.type == 'string'

entityTest11
entityTest11.properties.fieldB
entityTest11.properties.fieldB.type == 'integer'

entityTest2
entityTest2.properties.fieldA
entityTest2.properties.fieldA.type == 'string'

entityTest22
entityTest22.properties.fieldA
entityTest22.properties.fieldA.type == 'string'

entityTest3
entityTest3.properties.fieldC
entityTest3.properties.fieldC.type == 'string'

entityTest33
entityTest33.properties.fieldC
entityTest33.properties.fieldC.type == 'string'
}
}

0 comments on commit f5bbf85

Please sign in to comment.