Skip to content

Commit

Permalink
Merge pull request #835 from altro3/fixes_default_value
Browse files Browse the repository at this point in the history
Fix @QueryValue for List<String> parameters
  • Loading branch information
graemerocher authored Oct 31, 2022
2 parents a8f776f + f8c2577 commit 2588a14
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 22 deletions.
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ projectVersion=4.6.0-SNAPSHOT
projectGroup=io.micronaut.openapi

micronautDocsVersion=2.0.0
micronautVersion=3.7.1
micronautVersion=3.7.2
micronautTestVersion=3.7.0
groovyVersion=3.0.13
spockVersion=2.2-groovy-3.0
spockVersion=2.3-groovy-3.0

title=OpenAPI/Swagger Support
projectDesc=Configuration to integrate Micronaut and OpenAPI/Swagger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.fasterxml.jackson.databind.node.ObjectNode;

import static io.micronaut.openapi.visitor.ConvertUtils.normalizeValue;
import static io.micronaut.openapi.visitor.ConvertUtils.resolveExtensions;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.expandProperties;
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.getExpandableProperties;
Expand Down Expand Up @@ -742,7 +743,6 @@ private boolean isTypeNullable(ClassElement type) {
@Nullable
protected Schema resolveSchema(OpenAPI openAPI, @Nullable Element definingElement, ClassElement type, VisitorContext context,
List<MediaType> mediaTypes, JavadocDescription fieldJavadoc, JavadocDescription classJavadoc) {
Schema schema = null;

AnnotationValue<io.swagger.v3.oas.annotations.media.Schema> schemaAnnotationValue = null;
if (definingElement != null) {
Expand All @@ -766,6 +766,8 @@ protected Schema resolveSchema(OpenAPI openAPI, @Nullable Element definingElemen
}
}

Schema schema = null;

if (type instanceof EnumElement) {
schema = getSchemaDefinition(openAPI, context, type, definingElement, mediaTypes);
} else {
Expand Down Expand Up @@ -1136,7 +1138,7 @@ protected Schema bindSchemaForElement(VisitorContext context, Element element, C
final String defaultValue = element.getValue(Bindable.class, "defaultValue", String.class).orElse(null);
if (defaultValue != null && schemaToBind.getDefault() == null) {
try {
topLevelSchema.setDefault(ConvertUtils.normalizeValue(defaultValue, schemaToBind.getType(), schemaToBind.getFormat(), context));
topLevelSchema.setDefault(ConvertUtils.normalizeValue(defaultValue, schemaToBind.getType(), schemaToBind.getFormat(), context, true));
} catch (JsonProcessingException e) {
context.warn("Can't convert " + defaultValue + " to " + schemaToBind.getType() + ": " + e.getMessage(), element);
topLevelSchema.setDefault(defaultValue);
Expand Down Expand Up @@ -1541,10 +1543,14 @@ private void setSchemaDocumentation(Element element, Schema schemaToBind) {
* @return The bound schema
*/
protected Schema bindSchemaAnnotationValue(VisitorContext context, Element element, Schema schemaToBind, AnnotationValue<io.swagger.v3.oas.annotations.media.Schema> schemaAnn) {

ClassElement classElement = ((TypedElement) element).getType();
Pair<String, String> typeAndFormat = classElement.isIterable() ? Pair.of("array", null) : ConvertUtils.getTypeAndFormatByClass(classElement.getName());

JsonNode schemaJson = toJson(schemaAnn.getValues(), context);
return doBindSchemaAnnotationValue(context, element, schemaToBind, schemaJson,
schemaAnn.stringValue("type").orElse(null),
schemaAnn.stringValue("format").orElse(null),
schemaAnn.stringValue("type").orElse(typeAndFormat.getFirst()),
schemaAnn.stringValue("format").orElse(typeAndFormat.getSecond()),
schemaAnn.stringValue("defaultValue").orElse(null),
schemaAnn.get("allowableValues", String[].class).orElse(null));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@

import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.beans.BeanMap;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.swagger.ObjectMapperFactory;
import io.swagger.v3.oas.models.security.SecurityRequirement;
Expand Down Expand Up @@ -144,13 +143,17 @@ public static <T> T treeToValue(JsonNode jn, Class<T> clazz, VisitorContext cont
}

public static Object normalizeValue(String valueStr, String type, String format, VisitorContext context) throws JsonProcessingException {
return normalizeValue(valueStr, type, format, context, false);
}

public static Object normalizeValue(String valueStr, String type, String format, VisitorContext context, boolean isMicronautFormat) throws JsonProcessingException {
if (valueStr == null) {
return null;
}
if (type == null || type.equals("object")) {
return CONVERT_JSON_MAPPER.readValue(valueStr, Map.class);
}
return parseByTypeAndFormat(valueStr, type, format, context);
return parseByTypeAndFormat(valueStr, type, format, context, isMicronautFormat);
}

public static Optional<Map<String, Object>> resolveExtensions(JsonNode jn) {
Expand Down Expand Up @@ -259,13 +262,24 @@ public static Pair<String, String> getTypeAndFormatByClass(String className) {
* @param type openapi type
* @param format openapi value
* @param context visitor context
* @param isMicronautFormat is it micronaut format for arrays
*
* @return parsed value
*/
public static Object parseByTypeAndFormat(String valueStr, String type, String format, VisitorContext context) {
if (StringUtils.isEmpty(valueStr)) {
public static Object parseByTypeAndFormat(String valueStr, String type, String format, VisitorContext context, boolean isMicronautFormat) {
if (valueStr == null) {
return null;
}

// @QueryValue(defaultValue = "")
if ("array".equals(type) && isMicronautFormat) {
return valueStr.split(",");
}

if (valueStr.isEmpty()) {
return null;
}

try {
if ("string".equals(type)) {
if ("uri".equals(format)) {
Expand All @@ -279,6 +293,8 @@ public static Object parseByTypeAndFormat(String valueStr, String type, String f
}
} else if ("boolean".equals(type)) {
return Boolean.parseBoolean(valueStr);
} else if ("array".equals(type)) {
return JSON_MAPPER.readValue(valueStr, List.class);
} else if ("integer".equals(type)) {
if ("int32".equals(format)) {
return Integer.parseInt(valueStr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,92 @@ import spock.lang.Issue

class OpenApiParameterMappingSpec extends AbstractOpenApiTypeElementSpec {

void "test that @Parameter propagates correctly"() {
void "test @QueryValue list with defaultValue"() {

given: "An API definition"
when:
buildBeanDefinition('test.MyBean', '''
package test;
import io.reactivex.*;
import io.micronaut.http.annotation.*;
import io.micronaut.http.*;
import java.util.List;
import io.swagger.v3.oas.annotations.*;
import io.swagger.v3.oas.annotations.parameters.*;
import io.swagger.v3.oas.annotations.responses.*;
import io.swagger.v3.oas.annotations.security.*;
import io.swagger.v3.oas.annotations.media.*;
import io.swagger.v3.oas.annotations.enums.*;
import com.fasterxml.jackson.annotation.*;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;
import io.swagger.v3.oas.annotations.media.Schema;
@Controller
class NetworkOperations {
@Get
String method(@QueryValue(value = "test", defaultValue = "") List<String> items) {
return "";
}
@Get("/sec")
String method2(@QueryValue(value = "test", defaultValue = "a1,a2,a3") List<String> items) {
return "";
}
@Get("/third")
String method3(@Schema(defaultValue = "[]") @QueryValue("test") List<String> items) {
return "";
}
}
@jakarta.inject.Singleton
class MyBean {}
''')
then: "the state is correct"
Utils.testReference != null

when: "The OpenAPI is retrieved"
OpenAPI openAPI = Utils.testReference
PathItem pathItem = openAPI.paths.get("/")
PathItem pathItemSec = openAPI.paths.get("/sec")
PathItem pathItemThird = openAPI.paths.get("/third")

then: "it is included in the OpenAPI doc"
pathItem.get.parameters.size() == 1
pathItem.get.parameters[0].name == 'test'
pathItem.get.parameters[0].schema
pathItem.get.parameters[0].schema.type == 'array'
pathItem.get.parameters[0].schema.default
pathItem.get.parameters[0].schema.default[0] == ''

pathItemSec.get.parameters.size() == 1
pathItemSec.get.parameters[0].name == 'test'
pathItemSec.get.parameters[0].schema
pathItemSec.get.parameters[0].schema.type == 'array'
pathItemSec.get.parameters[0].schema.default
pathItemSec.get.parameters[0].schema.default[0] == 'a1'
pathItemSec.get.parameters[0].schema.default[1] == 'a2'
pathItemSec.get.parameters[0].schema.default[2] == 'a3'

pathItemThird.get.parameters.size() == 1
pathItemThird.get.parameters[0].name == 'test'
pathItemThird.get.parameters[0].schema
pathItemThird.get.parameters[0].schema.type == 'array'
pathItemThird.get.parameters[0].schema.default != null
pathItemThird.get.parameters[0].schema.default.size() == 0
}

void "test that @Parameter propagates correctly"() {

given: "An API definition"
when:
buildBeanDefinition('test.MyBean', '''
package test;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
/**
* @author graemerocher
Expand All @@ -51,7 +119,7 @@ interface NetworkOperations {
)
})
@Get
public HttpResponse<Greeting> getNetworks(
HttpResponse<Greeting> getNetworks(
@Parameter(
name = "fooBar",
description = "NA/true/false (case insensitive)",
Expand Down

0 comments on commit 2588a14

Please sign in to comment.