Skip to content

Commit

Permalink
Fix convert Operation annotation with groovy (#1419)
Browse files Browse the repository at this point in the history
  • Loading branch information
altro3 committed Feb 16, 2024
1 parent 7d6296d commit b789909
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
import static io.micronaut.openapi.visitor.SchemaUtils.isIgnoredHeader;
import static io.micronaut.openapi.visitor.SchemaUtils.setOperationOnPathItem;
import static io.micronaut.openapi.visitor.Utils.DEFAULT_MEDIA_TYPES;
import static io.micronaut.openapi.visitor.Utils.getMediaType;

/**
* A {@link io.micronaut.inject.visitor.TypeElementVisitor} the builds the Swagger model from Micronaut controllers at compile time.
Expand Down Expand Up @@ -486,22 +487,22 @@ private void processExtraBodyParameters(VisitorContext context, HttpMethod httpM
if (HttpMethod.permitsRequestBody(httpMethod) && !extraBodyParameters.isEmpty()) {
if (requestBody == null) {
requestBody = new RequestBody();
Content content = new Content();
var content = new Content();
requestBody.setContent(content);
requestBody.setRequired(true);
swaggerOperation.setRequestBody(requestBody);

consumesMediaTypes = CollectionUtils.isEmpty(consumesMediaTypes) ? DEFAULT_MEDIA_TYPES : consumesMediaTypes;
consumesMediaTypes.forEach(mediaType -> {
io.swagger.v3.oas.models.media.MediaType mt = new io.swagger.v3.oas.models.media.MediaType();
var mt = new io.swagger.v3.oas.models.media.MediaType();
var schema = new Schema<>();
schema.setType(TYPE_OBJECT);
mt.setSchema(schema);
content.addMediaType(mediaType.toString(), mt);
});
}
}
if (requestBody != null && !extraBodyParameters.isEmpty()) {
if (requestBody != null && requestBody.getContent() != null && !extraBodyParameters.isEmpty()) {
requestBody.getContent().forEach((mediaTypeName, mediaType) -> {
var schema = mediaType.getSchema();
if (schema == null) {
Expand All @@ -523,7 +524,7 @@ private void processExtraBodyParameters(VisitorContext context, HttpMethod httpM
}
for (TypedElement parameter : extraBodyParameters) {
if (!isRequestBodySchemaSet) {
processBodyParameter(context, openAPI, javadocDescription, MediaType.of(mediaTypeName), schema, parameter);
processBodyParameter(context, openAPI, javadocDescription, getMediaType(mediaTypeName), schema, parameter);
}
if (mediaTypeName.equals(MediaType.MULTIPART_FORM_DATA)) {
if (CollectionUtils.isNotEmpty(schema.getProperties())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ <T> Optional<T> toValue(Map<CharSequence, Object> values, VisitorContext context
try {
return Optional.ofNullable(ConvertUtils.treeToValue(node, type, context));
} catch (JsonProcessingException e) {
warn("Error converting [" + node + "]: to " + type + ": " + e.getMessage(), context);
warn("Error converting [" + node + "]: to " + type + ":\n" + Utils.printStackTrace(e), context);
}
return Optional.empty();
}
Expand Down Expand Up @@ -2277,7 +2277,7 @@ private List<Object> getEnumValues(EnumElement type, String schemaType, String s
try {
enumValues.add(ConvertUtils.normalizeValue(jacksonValue, schemaType, schemaFormat, context));
} catch (JsonProcessingException e) {
warn("Error converting jacksonValue " + jacksonValue + " : to " + type + ": " + e.getMessage(), context, element);
warn("Error converting jacksonValue " + jacksonValue + " : to " + type + ":\n" + Utils.printStackTrace(e), context, element);
enumValues.add(element.getSimpleName());
}
} else {
Expand Down
137 changes: 122 additions & 15 deletions openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@
import io.swagger.v3.oas.annotations.servers.Server;
import io.swagger.v3.oas.annotations.servers.ServerVariable;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.Encoding;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;

Expand Down Expand Up @@ -110,7 +117,7 @@ public static <T> T toValue(Map<CharSequence, Object> values, VisitorContext con
try {
return ConvertUtils.treeToValue(node, type, context);
} catch (JsonProcessingException e) {
warn("Error converting [" + node + "]: to " + type + ": " + e.getMessage(), context);
warn("Error converting [" + node + "]: to " + type + ":\n" + Utils.printStackTrace(e), context);
}
return null;
}
Expand Down Expand Up @@ -255,28 +262,22 @@ public static Optional<Object> parseJsonString(Object object) {
*/
public static <T> T treeToValue(JsonNode jn, Class<T> clazz, VisitorContext context) throws JsonProcessingException {

var fixed = false;
T value;
try {
value = CONVERT_JSON_MAPPER.treeToValue(jn, clazz);
} catch (Exception e) {
// fix for problem with groovy. Jackson throw exception with ApiResponse class
if (clazz == ApiResponse.class && jn.has("content")) {
var contentNode = jn.get("content");
((ObjectNode) jn).set("content", null);
value = CONVERT_JSON_MAPPER.treeToValue(jn, clazz);
var result = new Content();
if (contentNode.isArray()) {
for (var content : contentNode) {
processMediaType(result, content);
}
} else {
processMediaType(result, contentNode);
}
((ApiResponse) value).setContent(result);
// maybe exception with groovy
if (context.getLanguage() == VisitorContext.Language.GROOVY) {
value = fixForGroovy(jn, clazz, null);
fixed = true;
} else {
throw e;
}
}
if (!fixed && context.getLanguage() == VisitorContext.Language.GROOVY) {
value = fixForGroovy(jn, clazz, null);
}

if (value == null) {
return null;
Expand Down Expand Up @@ -320,6 +321,112 @@ public static <T> T treeToValue(JsonNode jn, Class<T> clazz, VisitorContext cont
return value;
}

private static <T> Map<String, T> deserMap(String name, JsonNode jn, Class<T> clazz) throws JsonProcessingException {

var mapNode = jn.get(name);
if (mapNode == null) {
return null;
}
((ObjectNode) jn).remove(name);

var iter = mapNode.fieldNames();
var result = new HashMap<String, T>();
while (iter.hasNext()) {
var entryKey = iter.next();
var objectNode = mapNode.get(entryKey);
var object = CONVERT_JSON_MAPPER.treeToValue(objectNode, clazz);
result.put(entryKey, object);
}
return !result.isEmpty() ? result : null;
}

private static <T> T fixForGroovy(JsonNode jn, Class<T> clazz, Exception e) throws JsonProcessingException {

// fix for problem with groovy. Jackson throw exception with Operation class with content and mediaType
// see https://github.com/micronaut-projects/micronaut-openapi/issues/1418
if (clazz == Operation.class) {

var requestBodyNode = jn.get("requestBody");
((ObjectNode) jn).remove("requestBody");
T value = CONVERT_JSON_MAPPER.treeToValue(jn, clazz);
var requestBody = fixContentForGroovy(requestBodyNode, RequestBody.class);
((Operation) value).setRequestBody(requestBody);

var responsesNode = jn.get("responses");
((ObjectNode) jn).remove("responses");
ApiResponses responses = null;
if (responsesNode != null && !responsesNode.isEmpty()) {
responses = new ApiResponses();
var iter = responsesNode.fields();
while (iter.hasNext()) {
var entry = iter.next();
responses.put(entry.getKey(), fixContentForGroovy(entry.getValue(), ApiResponse.class));
}
}
((Operation) value).setResponses(responses);
return value;
} else if (clazz == ApiResponse.class
|| clazz == Header.class
|| clazz == Parameter.class
|| clazz == RequestBody.class) {
return fixContentForGroovy(jn, clazz);
} else {
return CONVERT_JSON_MAPPER.treeToValue(jn, clazz);
}
}

private static <T> T fixContentForGroovy(JsonNode parentNode, Class<T> clazz) throws JsonProcessingException {
if (parentNode == null) {
return null;
}
Map<String, Example> examples = null;
Map<String, Encoding> encoding = null;
Map<String, Object> extensions = null;
Schema<?> schema = null;
JsonNode mediaTypeNode = null;

var contentNode = parentNode.get("content");
if (contentNode != null) {
examples = deserMap("examples", contentNode, Example.class);
encoding = deserMap("encoding", contentNode, Encoding.class);
extensions = deserMap("extensions", contentNode, Object.class);
var schemaNode = contentNode.get("schema");
if (schemaNode != null) {
schema = CONVERT_JSON_MAPPER.treeToValue(schemaNode, Schema.class);
}

mediaTypeNode = contentNode.get("mediaType");
((ObjectNode) contentNode).remove("mediaType");
}
var value = CONVERT_JSON_MAPPER.treeToValue(parentNode, clazz);
Content content = null;
if (value instanceof ApiResponse apiResponse) {
content = apiResponse.getContent();
} else if (value instanceof Header header) {
content = header.getContent();
} else if (value instanceof Parameter parameter) {
content = parameter.getContent();
} else if (value instanceof RequestBody requestBody) {
content = requestBody.getContent();
}

if (content != null) {
var mediaType = content.get("schema");
content.remove("schema");
if (mediaType == null) {
mediaType = new MediaType();
}
var contentType = mediaTypeNode != null ? mediaTypeNode.textValue() : io.micronaut.http.MediaType.APPLICATION_JSON;
content.put(contentType, mediaType);
mediaType.setExamples(examples);
mediaType.setEncoding(encoding);
mediaType.setExtensions(extensions);
mediaType.setSchema(schema);
}

return value;
}

private static void processMediaType(Content result, JsonNode content) throws JsonProcessingException {
var mediaType = content.has("mediaType") ? content.get("mediaType").asText() : io.micronaut.http.MediaType.APPLICATION_JSON;
var mediaTypeObj = CONVERT_JSON_MAPPER.treeToValue(content, MediaType.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private List<MediaType> mediaTypes(MethodElement element, Class<? extends Annota
return DEFAULT_MEDIA_TYPES;
}
return Arrays.stream(values)
.map(MediaType::of)
.map(Utils::getMediaType)
.distinct()
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private static List<MediaType> mediaTypes(String... arr) {
return DEFAULT_MEDIA_TYPES;
}
return Arrays.stream(arr)
.map(MediaType::of)
.map(Utils::getMediaType)
.toList();
}

Expand Down
14 changes: 14 additions & 0 deletions openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ public final class Utils {
private Utils() {
}

/**
* Get or create MediaType object by name.
*
* @param mediaTypeName name of mediaType
* @return MediaType object
*/
public static MediaType getMediaType(String mediaTypeName) {
try {
return MediaType.of(mediaTypeName);
} catch (Exception e) {
return new MediaType(mediaTypeName);
}
}

/**
* @return An Instance of default {@link PropertyPlaceholderResolver} to resolve placeholders.
*/
Expand Down
Loading

0 comments on commit b789909

Please sign in to comment.