Skip to content

Commit

Permalink
Merge pull request #867 from altro3/fixes_858
Browse files Browse the repository at this point in the history
Changed processing logic for inheritance. 

Fixed #858
  • Loading branch information
graemerocher committed Dec 2, 2022
2 parents 35818d3 + 85a6be2 commit 3bbcdc2
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 86 deletions.
21 changes: 11 additions & 10 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# Auto detect text files and perform LF normalization
* text=auto
* text=lf

*.java text
*.html text
*.kt text
*.kts text
*.md text diff=markdown
*.java text eol=lf
*.groovy text eol=lf
*.html text eol=lf
*.kt text eol=lf
*.kts text eol=lf
*.md text diff=markdown eol=lf
*.py text diff=python executable
*.pl text diff=perl executable
*.pm text diff=perl
*.css text diff=css
*.js text
*.sql text
*.q text
*.css text diff=css eol=lf
*.js text eol=lf
*.sql text eol=lf
*.q text eol=lf

*.sh text eol=lf
gradlew text eol=lf
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1811,47 +1811,10 @@ private Schema getSchemaDefinition(
schema.setEnum(getEnumValues((EnumElement) type, schema.getType(), schema.getFormat(), context));
}
} else {
if (type instanceof TypedElement) {
ClassElement classElement = ((TypedElement) type).getType();

List<ClassElement> superTypes = new ArrayList<>();
Collection<ClassElement> parentInterfaces = classElement.getInterfaces();
if (classElement.isInterface() && !parentInterfaces.isEmpty()) {
for (ClassElement parentInterface : parentInterfaces) {
if (ClassUtils.isJavaLangType(parentInterface.getName())
|| parentInterface.getBeanProperties().isEmpty()) {
continue;
}
superTypes.add(parentInterface);
}
} else {
classElement.getSuperType().ifPresent(superTypes::add);
}

if (!type.isRecord() && !superTypes.isEmpty()) {
schema = new ComposedSchema();
for (ClassElement sType : superTypes) {
if (!type.isRecord()) {
Map<String, ClassElement> sTypeArgs = sType.getTypeArguments();
ClassElement customStype = OpenApiApplicationVisitor.getCustomSchema(sType.getName(), sTypeArgs, context);
if (customStype != null) {
sType = customStype;
}
readAllInterfaces(openAPI, context, definingElement, mediaTypes, schema, sType, schemas, sTypeArgs);
}
}
} else {
schema = new Schema();
}
} else {
schema = new Schema();
}
schema.setType("object");
schema.setName(schemaName);
schema = processSuperTypes(null, schemaName, type, definingElement, openAPI, mediaTypes, schemas, context);
if (javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
schema.setDescription(javadoc.getMethodDescription());
}
schemas.put(schemaName, schema);

populateSchemaProperties(openAPI, context, type, typeArgs, schema, mediaTypes, javadoc);
checkAllOf(schema);
Expand Down Expand Up @@ -1884,36 +1847,7 @@ private Schema getSchemaDefinition(
}

if (schema != null) {

ClassElement classElement = ((TypedElement) type).getType();
List<ClassElement> superTypes = new ArrayList<>();
Collection<ClassElement> parentInterfaces = classElement.getInterfaces();
if (classElement.isInterface() && !parentInterfaces.isEmpty()) {
for (ClassElement parentInterface : parentInterfaces) {
if (ClassUtils.isJavaLangType(parentInterface.getName())
|| parentInterface.getBeanProperties().isEmpty()) {
continue;
}
superTypes.add(parentInterface);
}
}
if (!superTypes.isEmpty()) {
ComposedSchema schema1 = new ComposedSchema();
schema1.addAllOfItem(schema);

for (ClassElement sType : superTypes) {
ClassElement customStype = OpenApiApplicationVisitor.getCustomSchema(sType.getName(), sType.getTypeArguments(), context);
String schemaName2 = computeDefaultSchemaName(definingElement, customStype != null ? customStype : sType, sType.getTypeArguments(), context);
Schema parentSchema = new Schema();
parentSchema.set$ref(SchemaUtils.schemaRef(schemaName2));
schema1.addAllOfItem(parentSchema);
}

schema = schema1;
}

schema.setName(schemaName);
schemas.put(schemaName, schema);
processSuperTypes(schema, schemaName, type, definingElement, openAPI, mediaTypes, schemas, context);
}
} catch (JsonProcessingException e) {
context.warn("Error reading Swagger Parameter for element [" + type + "]: " + e.getMessage(), type);
Expand Down Expand Up @@ -1942,6 +1876,53 @@ private Schema getSchemaDefinition(
return null;
}

private Schema processSuperTypes(Schema schema,
String schemaName,
ClassElement type, @Nullable Element definingElement,
OpenAPI openAPI,
List<MediaType> mediaTypes,
Map<String, Schema> schemas,
VisitorContext context) {
ClassElement classElement = ((TypedElement) type).getType();
List<ClassElement> superTypes = new ArrayList<>();
Collection<ClassElement> parentInterfaces = classElement.getInterfaces();
if (classElement.isInterface() && !parentInterfaces.isEmpty()) {
for (ClassElement parentInterface : parentInterfaces) {
if (ClassUtils.isJavaLangType(parentInterface.getName())
|| parentInterface.getBeanProperties().isEmpty()) {
continue;
}
superTypes.add(parentInterface);
}
} else {
classElement.getSuperType().ifPresent(superTypes::add);
}
if (!type.isRecord() && !superTypes.isEmpty()) {
if (schema == null) {
schema = new ComposedSchema();
schema.setType("object");
}
for (ClassElement sType : superTypes) {
Map<String, ClassElement> sTypeArgs = sType.getTypeArguments();
ClassElement customStype = OpenApiApplicationVisitor.getCustomSchema(sType.getName(), sTypeArgs, context);
if (customStype != null) {
sType = customStype;
}
readAllInterfaces(openAPI, context, definingElement, mediaTypes, schema, sType, schemas, sTypeArgs);
}
} else {
if (schema == null) {
schema = new Schema();
schema.setType("object");
}
}

schema.setName(schemaName);
schemas.put(schemaName, schema);

return schema;
}

@SuppressWarnings("java:S3655") // false positive
private void readAllInterfaces(OpenAPI openAPI, VisitorContext context, @Nullable Element definingElement, List<MediaType> mediaTypes,
Schema schema, ClassElement superType, Map<String, Schema> schemas, Map<String, ClassElement> superTypeArgs) {
Expand All @@ -1950,7 +1931,9 @@ private void readAllInterfaces(OpenAPI openAPI, VisitorContext context, @Nullabl
|| getSchemaDefinition(openAPI, context, superType, superTypeArgs, null, mediaTypes) != null) {
Schema parentSchema = new Schema();
parentSchema.set$ref(SchemaUtils.schemaRef(parentSchemaName));
schema.addAllOfItem(parentSchema);
if (schema.getAllOf() == null || !schema.getAllOf().contains(parentSchema)) {
schema.addAllOfItem(parentSchema);
}
}
if (superType.isInterface()) {
for (ClassElement interfaceElement : superType.getInterfaces()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import io.micronaut.openapi.swagger.ObjectMapperFactory;
import io.swagger.v3.oas.models.security.SecurityRequirement;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1219,8 +1219,8 @@ class MyBean {}
sleeperSchema != null
catSchema != null

petSchema.type == null
petSchema.properties == null
petSchema.type == 'object'
petSchema.properties.size() == 2
animalSchema.type == 'object'
animalSchema.properties.size() == 1
sleeperSchema.type == 'object'
Expand All @@ -1235,10 +1235,8 @@ class MyBean {}
catSchema.allOf[3].type == 'object'
catSchema.allOf[3].properties['clawSize'].type == 'integer'

petSchema.allOf.size() == 2
petSchema.allOf[0].type == 'object'
petSchema.allOf[0].properties.size() == 2
petSchema.allOf[1].$ref == '#/components/schemas/Animal'
petSchema.allOf.size() == 1
petSchema.allOf[0].$ref == '#/components/schemas/Animal'
}

void "test build OpenAPI for interface inheritance with generics"() {
Expand Down Expand Up @@ -1441,4 +1439,102 @@ class MyBean {}
opParentRequest
}

void "test class inheritance Schema"() {

given: "An API definition"
when:
buildBeanDefinition('test.MyBean', '''
package test;
import java.util.List;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.swagger.v3.oas.annotations.media.Schema;
@Controller
class MyOperations {
@Post("/get")
void getRequest(@Body MyDto dto) {
}
}
@Schema(name = "MyDto", description = "This is filter")
@Introspected
class MyDto extends FilterDto {
@Schema(hidden = true)
private final String entityType;
private final String entitySubType;
@Schema(description = "My description")
private final List<RelationFilter> relationFilters;
MyDto(String entityType, String entitySubType, List<RelationFilter> relationFilters, Integer page) {
super(page);
this.entityType = entityType;
this.entitySubType = entitySubType;
this.relationFilters = relationFilters;
}
public String getEntityType() {
return entityType;
}
public String getEntitySubType() {
return entitySubType;
}
public List<RelationFilter> getRelationFilters() {
return relationFilters;
}
}
@Schema(name = "FilterDto", description = "This is parent filter")
@Introspected
class FilterDto {
@Schema(name="page", description = "Page description", type = "integer", defaultValue = "0")
private final Integer page;
FilterDto(Integer page) {
this.page = page;
}
public Integer getPage() {
return page;
}
}
@Introspected
class RelationFilter {
}
@jakarta.inject.Singleton
class MyBean {}
''')
then: "the state is correct"
Utils.testReference != null

when: "The OpenAPI is retrieved"
OpenAPI openAPI = Utils.testReference
Schema myDtoSchema = openAPI.components.schemas.MyDto
Schema filterDtoSchema = openAPI.components.schemas.FilterDto

then:
myDtoSchema
!myDtoSchema.properties.entityType
myDtoSchema.properties.entitySubType
myDtoSchema.properties.relationFilters
myDtoSchema.allOf
myDtoSchema.allOf.size() == 1
myDtoSchema.allOf[0].$ref == '#/components/schemas/FilterDto'

filterDtoSchema
filterDtoSchema.properties.page
filterDtoSchema.properties.page.type == 'integer'
filterDtoSchema.properties.page.default == 0
}
}

0 comments on commit 3bbcdc2

Please sign in to comment.