Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for Issue #157 - Inherited class tags not propagated to methods (… #176

Merged
merged 2 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Expand Down Expand Up @@ -266,46 +264,32 @@ private <T, A extends Annotation> List<T> processOpenApiAnnotation(ClassElement

}
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