Skip to content

Commit

Permalink
Fix for Issue #157 - Inherited class tags not propagated to methods (… (
Browse files Browse the repository at this point in the history
#176)

* Fix for Issue #157 - Inherited class tags not propagated to methods (at least in kotlin)

#157

* refactoring: new method that convert to JSON node and to instance
  • Loading branch information
croudet authored Mar 26, 2020
1 parent 5766b3a commit 870d149
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,24 @@ JsonNode toJson(Map<CharSequence, Object> values, VisitorContext context) {
return jsonMapper.valueToTree(newValues);
}

/**
* Convert the given Map to a JSON node and then to the specified type.
* @param <T> The output class type
* @param values The values
* @param context The visitor context
* @param type The class
* @return The converted instance
*/
<T> Optional<T> toValue(Map<CharSequence, Object> values, VisitorContext context, Class<T> type) {
JsonNode node = toJson(values, context);
try {
return Optional.of(treeToValue(node, type));
} catch (JsonProcessingException e) {
context.warn("Error converting [" + node + "]: to " + type + ": " + e.getMessage(), null);
}
return Optional.empty();
}

/**
* Resolve the PathItem for the given {@link UriMatchTemplate}.
*
Expand Down Expand Up @@ -419,15 +437,8 @@ private <T extends Schema> void processAnnotationValue(VisitorContext context, A
.filter(entry -> filters == null || ! filters.contains(entry.getKey()))
.collect(toMap(
e -> e.getKey().equals("requiredProperties") ? "required" : e.getKey(), Map.Entry::getValue));
JsonNode schemaJson = toJson(values, context);
try {
T schema = treeToValue(schemaJson, type);
if (schema != null) {
schemaToValueMap(arraySchemaMap, schema);
}
} catch (JsonProcessingException e) {
context.warn("Error reading Swagger Schema: " + e.getMessage(), null);
}
Optional<T> schema = toValue(values, context, type);
schema.ifPresent(s -> schemaToValueMap(arraySchemaMap, s));
}

private Map<CharSequence, Object> resolveArraySchemaAnnotationValues(VisitorContext context, AnnotationValue<?> av) {
Expand Down Expand Up @@ -1029,11 +1040,11 @@ protected Schema readSchema(AnnotationValue<io.swagger.v3.oas.annotations.media.
.entrySet()
.stream()
.collect(toMap(e -> e.getKey().equals("requiredProperties") ? "required" : e.getKey(), Map.Entry::getValue));
JsonNode schemaJson = toJson(values, context);
Schema schema = treeToValue(schemaJson, Schema.class);
if (schema == null) {
Optional<Schema> schemaOpt = toValue(values, context, Schema.class);
if (!schemaOpt.isPresent()) {
return null;
}
Schema schema = schemaOpt.get();
ComposedSchema composedSchema = null;
if (schema instanceof ComposedSchema) {
composedSchema = (ComposedSchema) schema;
Expand Down Expand Up @@ -1226,13 +1237,15 @@ private boolean isContainerType(ClassElement type) {
}

/**
* Processes {@link io.swagger.v3.oas.annotations.security.SecurityScheme} annotations.
* Processes {@link io.swagger.v3.oas.annotations.security.SecurityScheme}
* annotations.
*
* @param element The element
* @param context The visitor context
*/
protected void processSecuritySchemes(ClassElement element, VisitorContext context) {
final List<AnnotationValue<io.swagger.v3.oas.annotations.security.SecurityScheme>> values = element.getAnnotationValuesByType(io.swagger.v3.oas.annotations.security.SecurityScheme.class);
final List<AnnotationValue<io.swagger.v3.oas.annotations.security.SecurityScheme>> values = element
.getAnnotationValuesByType(io.swagger.v3.oas.annotations.security.SecurityScheme.class);
final OpenAPI openAPI = resolveOpenAPI(context);
for (AnnotationValue<io.swagger.v3.oas.annotations.security.SecurityScheme> securityRequirementAnnotationValue : values) {

Expand All @@ -1245,25 +1258,17 @@ protected void processSecuritySchemes(ClassElement element, VisitorContext conte
} else {
map.remove("name");
}
normalizeEnumValues(map, CollectionUtils.mapOf(
"type", SecurityScheme.Type.class, "in", SecurityScheme.In.class
));
final JsonNode jsonNode = toJson(map, context);
try {
final Optional<SecurityScheme> securityRequirement = Optional.of(treeToValue(jsonNode, SecurityScheme.class));
securityRequirement.ifPresent(securityScheme -> {
normalizeEnumValues(map, CollectionUtils.mapOf("type", SecurityScheme.Type.class, "in", SecurityScheme.In.class));
Optional<SecurityScheme> securityRequirement = toValue(map, context, SecurityScheme.class);
securityRequirement.ifPresent(securityScheme -> {

try {
securityScheme.setIn(Enum.valueOf(SecurityScheme.In.class, map.get("in").toString().toUpperCase(Locale.ENGLISH)));
} catch (Exception e) {
// ignore
}
resolveComponents(openAPI).addSecuritySchemes(name, securityScheme);
}
);
} catch (JsonProcessingException e) {
context.warn("Error reading Swagger SecurityRequirement for element [" + element + "]: " + e.getMessage(), element);
}
try {
securityScheme.setIn(Enum.valueOf(SecurityScheme.In.class, map.get("in").toString().toUpperCase(Locale.ENGLISH)));
} catch (Exception e) {
// ignore
}
resolveComponents(openAPI).addSecuritySchemes(name, securityScheme);
});
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package io.micronaut.openapi.visitor;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.PropertyNamingStrategyBase;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -279,46 +277,32 @@ private <T, A extends Annotation> List<T> processOpenApiAnnotation(ClassElement
tagList = new ArrayList<>();
}
for (AnnotationValue<A> tag : annotations) {
JsonNode jsonNode;
Map<CharSequence, Object> values;
if (tag.getAnnotationName().equals(SecurityRequirement.class.getName()) && tag.getValues().size() > 0) {
Object name = tag.getValues().get("name");
Object scopes = Optional.ofNullable(tag.getValues().get("scopes")).orElse(new ArrayList<String>());
jsonNode = toJson(Collections.singletonMap((CharSequence) name, scopes), context);
values = Collections.singletonMap((CharSequence) name, scopes);
} else {
jsonNode = toJson(tag.getValues(), context);
}
try {
T t = treeToValue(jsonNode, modelType);
if (t != null) {
tagList.add(t);
}
} catch (JsonProcessingException e) {
context.warn("Error reading OpenAPI" + annotationType + " annotation", element);
values = tag.getValues();
}
toValue(values, context, modelType).ifPresent(tagList::add);
}
}
return tagList;
}

private OpenAPI readOpenAPI(ClassElement element, VisitorContext context) {
return element.findAnnotation(OpenAPIDefinition.class).flatMap(o -> {
JsonNode jsonNode = toJson(o.getValues(), context);

try {
Optional<OpenAPI> result = Optional.of(treeToValue(jsonNode, OpenAPI.class));
result.ifPresent(openAPI -> {
List<io.swagger.v3.oas.models.security.SecurityRequirement> securityRequirements =
o.getAnnotations("security", io.swagger.v3.oas.annotations.security.SecurityRequirement.class)
.stream()
.map(this::mapToSecurityRequirement)
.collect(Collectors.toList());
openAPI.setSecurity(securityRequirements);
});
return result;
} catch (JsonProcessingException e) {
context.warn("Error reading Swagger OpenAPI for element [" + element + "]: " + e.getMessage(), element);
return Optional.empty();
}
Optional<OpenAPI> result = toValue(o.getValues(), context, OpenAPI.class);
result.ifPresent(openAPI -> {
List<io.swagger.v3.oas.models.security.SecurityRequirement> securityRequirements =
o.getAnnotations("security", io.swagger.v3.oas.annotations.security.SecurityRequirement.class)
.stream()
.map(this::mapToSecurityRequirement)
.collect(Collectors.toList());
openAPI.setSecurity(securityRequirements);
});
return result;
}).orElse(new OpenAPI());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,14 @@
*/
@Experimental
public class OpenApiControllerVisitor extends AbstractOpenApiVisitor implements TypeElementVisitor<Controller, HttpMethodMapping> {
private static final String CLASS_TAGS = "CLASS_TAGS";

private PropertyPlaceholderResolver propertyPlaceholderResolver;

@Override
public void visitClass(ClassElement element, VisitorContext context) {
processSecuritySchemes(element, context);
context.put(CLASS_TAGS, readTags(element, context));
}

private boolean hasNoBindingAnnotationOrType(ParameterElement parameter) {
Expand All @@ -122,6 +124,7 @@ private boolean hasNoBindingAnnotationOrType(ParameterElement parameter) {
!parameter.getType().isAssignable("io.micronaut.http.BasicAuth");
}

@SuppressWarnings("unchecked")
@Override
public void visitMethod(MethodElement element, VisitorContext context) {
if (element.isAnnotationPresent(Hidden.class)) {
Expand Down Expand Up @@ -152,19 +155,9 @@ public void visitMethod(MethodElement element, VisitorContext context) {
OpenAPI openAPI = resolveOpenAPI(context);

final Optional<AnnotationValue<Operation>> operationAnnotation = element.findAnnotation(Operation.class);
io.swagger.v3.oas.models.Operation swaggerOperation = operationAnnotation.flatMap(o -> {
JsonNode jsonNode = toJson(o.getValues(), context);

try {
return Optional.of(treeToValue(jsonNode, io.swagger.v3.oas.models.Operation.class));
} catch (Exception e) {
context.warn("Error reading Swagger Operation for element [" + element + "]: " + e.getMessage(), element);
return Optional.empty();
}
}).orElse(new io.swagger.v3.oas.models.Operation());

readTags(element, swaggerOperation);

io.swagger.v3.oas.models.Operation swaggerOperation = operationAnnotation.flatMap(o ->
toValue(o.getValues(), context, io.swagger.v3.oas.models.Operation.class)).orElse(new io.swagger.v3.oas.models.Operation());
readTags(element, swaggerOperation, (List<io.swagger.v3.oas.models.tags.Tag>) context.get(CLASS_TAGS, List.class, Collections.emptyList()));
readSecurityRequirements(element, context, swaggerOperation);

readApiResponses(element, context, swaggerOperation);
Expand Down Expand Up @@ -596,34 +589,17 @@ private void readApiResponses(MethodElement element, VisitorContext context, io.
if (CollectionUtils.isNotEmpty(responseAnnotations)) {
ApiResponses apiResponses = new ApiResponses();
for (AnnotationValue<io.swagger.v3.oas.annotations.responses.ApiResponse> r : responseAnnotations) {

JsonNode jn = toJson(r.getValues(), context);
try {
Optional<ApiResponse> newResponse = Optional.of(treeToValue(jn, ApiResponse.class));
newResponse.ifPresent(apiResponse -> {
String name = r.get("responseCode", String.class).orElse("default");
apiResponses.put(name, apiResponse);
});
} catch (Exception e) {
context.warn("Error reading Swagger ApiResponses for element [" + element + "]: " + e.getMessage(), element);
}
Optional<ApiResponse> newResponse = toValue(r.getValues(), context, ApiResponse.class);
newResponse.ifPresent(apiResponse ->
apiResponses.put(r.get("responseCode", String.class).orElse("default"), apiResponse));
}
swaggerOperation.setResponses(apiResponses);
}
}

private void readSwaggerRequestBody(Element element, VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation) {
element.findAnnotation(io.swagger.v3.oas.annotations.parameters.RequestBody.class)
.flatMap(annotation -> {
JsonNode jn = toJson(annotation.getValues(), context);
try {
return Optional.of(treeToValue(jn, RequestBody.class));
} catch (Exception e) {
context.warn("Error reading Swagger ResponseBody for element [" + element + "]: " + e.getMessage(), element);
return Optional.empty();
}

})
.flatMap(annotation -> toValue(annotation.getValues(), context, RequestBody.class))
.ifPresent(swaggerOperation::setRequestBody);
}

Expand All @@ -644,13 +620,7 @@ private void readServers(MethodElement element, VisitorContext context, io.swagg
List<AnnotationValue<io.swagger.v3.oas.annotations.servers.Server>> serverAnnotations = element.getAnnotationValuesByType(io.swagger.v3.oas.annotations.servers.Server.class);
if (CollectionUtils.isNotEmpty(serverAnnotations)) {
for (AnnotationValue<io.swagger.v3.oas.annotations.servers.Server> r : serverAnnotations) {
JsonNode jn = toJson(r.getValues(), context);
try {
Optional<Server> newRequirement = Optional.of(treeToValue(jn, Server.class));
newRequirement.ifPresent(swaggerOperation::addServersItem);
} catch (Exception e) {
context.warn("Error reading Swagger Server for element [" + element + "]: " + e.getMessage(), element);
}
toValue(r.getValues(), context, Server.class).ifPresent(swaggerOperation::addServersItem);
}
}
}
Expand All @@ -677,16 +647,8 @@ private void readCallbacks(MethodElement element, VisitorContext context, io.swa
final PathItem pathItem = new PathItem();
for (AnnotationValue<Operation> operation : operations) {
final Optional<HttpMethod> operationMethod = operation.get("method", HttpMethod.class);
operationMethod.ifPresent(httpMethod -> {
JsonNode jsonNode = toJson(operation.getValues(), context);

try {
final Optional<io.swagger.v3.oas.models.Operation> op = Optional.of(treeToValue(jsonNode, io.swagger.v3.oas.models.Operation.class));
op.ifPresent(operation1 -> setOperationOnPathItem(pathItem, operation1, httpMethod));
} catch (Exception e) {
context.warn("Error reading Swagger Operation for element [" + element + "]: " + e.getMessage(), element);
}
});
operationMethod.ifPresent(httpMethod ->
toValue(operation.getValues(), context, io.swagger.v3.oas.models.Operation.class).ifPresent(op -> setOperationOnPathItem(pathItem, op, httpMethod)));
}
Map<String, io.swagger.v3.oas.models.callbacks.Callback> callbacks = initCallbacks(swaggerOperation);
final io.swagger.v3.oas.models.callbacks.Callback c = new io.swagger.v3.oas.models.callbacks.Callback();
Expand Down Expand Up @@ -720,13 +682,36 @@ private Map<String, io.swagger.v3.oas.models.callbacks.Callback> initCallbacks(i
return callbacks;
}

private void readTags(MethodElement element, io.swagger.v3.oas.models.Operation swaggerOperation) {
private void readTags(MethodElement element, io.swagger.v3.oas.models.Operation swaggerOperation, List<io.swagger.v3.oas.models.tags.Tag> classTags) {
List<AnnotationValue<Tag>> tagAnnotations = element.getAnnotationValuesByType(Tag.class);
if (CollectionUtils.isNotEmpty(tagAnnotations)) {
for (AnnotationValue<Tag> r : tagAnnotations) {
r.get("name", String.class).ifPresent(swaggerOperation::addTagsItem);
}
}
if (!classTags.isEmpty()) {
List<String> operationTags = swaggerOperation.getTags();
if (operationTags == null) {
operationTags = new ArrayList<>(classTags.size());
swaggerOperation.setTags(operationTags);
}
for (io.swagger.v3.oas.models.tags.Tag tag : classTags) {
if (!operationTags.contains(tag.getName())) {
operationTags.add(tag.getName());
}
}
}
}

private List<io.swagger.v3.oas.models.tags.Tag> readTags(ClassElement element, VisitorContext context) {
List<io.swagger.v3.oas.models.tags.Tag> tagList = new ArrayList<>();
List<AnnotationValue<Tag>> tagAnnotations = element.getAnnotationValuesByType(Tag.class);
if (CollectionUtils.isNotEmpty(tagAnnotations)) {
for (AnnotationValue<Tag> tag : tagAnnotations) {
toValue(tag.getValues(), context, io.swagger.v3.oas.models.tags.Tag.class).ifPresent(tagList::add);
}
}
return tagList;
}

private Content buildContent(Element definingElement, ClassElement type, String mediaType, OpenAPI openAPI, VisitorContext context) {
Expand Down

0 comments on commit 870d149

Please sign in to comment.