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

Update Mixins to latest from swagger and handle explode attribute in Parameter #163

Merged
merged 2 commits into from
Mar 4, 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
4 changes: 3 additions & 1 deletion docs-examples/example-groovy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ plugins {

dependencyManagement {
imports {
mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
mavenBom("io.micronaut:micronaut-bom:$micronautVersion") {
bomProperty 'swagger.version', swaggerVersion
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion docs-examples/example-java/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ plugins {

dependencyManagement {
imports {
mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
mavenBom("io.micronaut:micronaut-bom:$micronautVersion") {
bomProperty 'swagger.version', swaggerVersion
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion docs-examples/example-kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ plugins {

dependencyManagement {
imports {
mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
mavenBom("io.micronaut:micronaut-bom:$micronautVersion") {
bomProperty 'swagger.version', swaggerVersion
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ grailsVersion=3.3.8
spockVersion=1.2-groovy-2.5
micronautTestVersion=1.1.0
rxJava2Version=2.2.6
swaggerVersion=2.0.10
swaggerVersion=2.1.1
title=OpenAPI/Swagger Support
projectDesc=Configuration to integrate Micronaut and OpenAPI/Swagger
projectUrl=http://micronaut.io
Expand Down
79 changes: 42 additions & 37 deletions openapi/src/main/java/io/micronaut/openapi/util/Yaml.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@

import io.micronaut.core.annotation.Internal;
import io.swagger.v3.core.jackson.SchemaSerializer;
import io.swagger.v3.core.jackson.mixin.ComponentsMixin;
import io.swagger.v3.core.jackson.mixin.DateSchemaMixin;
import io.swagger.v3.core.jackson.mixin.ExtensionsMixin;
import io.swagger.v3.core.jackson.mixin.OpenAPIMixin;
import io.swagger.v3.core.jackson.mixin.OperationMixin;
import io.swagger.v3.core.util.DeserializationModule;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.callbacks.Callback;
Expand All @@ -43,6 +47,7 @@
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.OAuthFlow;
import io.swagger.v3.oas.models.security.OAuthFlows;
import io.swagger.v3.oas.models.security.Scopes;
Expand All @@ -52,10 +57,8 @@
import io.swagger.v3.oas.models.servers.ServerVariables;
import io.swagger.v3.oas.models.tags.Tag;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Helper class for generating Swagger YAML.
Expand Down Expand Up @@ -100,40 +103,42 @@ public JsonSerializer<?> modifySerializer(
}
}).registerModule(new DeserializationModule()).registerModule(new JavaTimeModule());

List<Class<?>> mixinTargets = Arrays.asList(
ApiResponse.class,
Callback.class,
Components.class,
Contact.class,
Encoding.class,
EncodingProperty.class,
Example.class,
ExternalDocumentation.class,
Header.class,
Info.class,
License.class,
Link.class,
LinkParameter.class,
MediaType.class,
OAuthFlow.class,
OAuthFlows.class,
OpenAPI.class,
Operation.class,
Parameter.class,
PathItem.class,
Paths.class,
RequestBody.class,
Scopes.class,
SecurityScheme.class,
Server.class,
ServerVariable.class,
ServerVariables.class,
Tag.class,
XML.class,
Schema.class
);
mapper.setMixIns(mixinTargets.stream().collect(Collectors.toMap(Function.identity(), c -> ExtensionsMixin.class)));
Map<Class<?>, Class<?>> sourceMixins = new LinkedHashMap<>(40);

sourceMixins.put(ApiResponses.class, ExtensionsMixin.class);
sourceMixins.put(ApiResponse.class, ExtensionsMixin.class);
sourceMixins.put(Callback.class, ExtensionsMixin.class);
sourceMixins.put(Components.class, ComponentsMixin.class);
sourceMixins.put(Contact.class, ExtensionsMixin.class);
sourceMixins.put(Encoding.class, ExtensionsMixin.class);
sourceMixins.put(EncodingProperty.class, ExtensionsMixin.class);
sourceMixins.put(Example.class, ExtensionsMixin.class);
sourceMixins.put(ExternalDocumentation.class, ExtensionsMixin.class);
sourceMixins.put(Header.class, ExtensionsMixin.class);
sourceMixins.put(Info.class, ExtensionsMixin.class);
sourceMixins.put(License.class, ExtensionsMixin.class);
sourceMixins.put(Link.class, ExtensionsMixin.class);
sourceMixins.put(LinkParameter.class, ExtensionsMixin.class);
sourceMixins.put(MediaType.class, ExtensionsMixin.class);
sourceMixins.put(OAuthFlow.class, ExtensionsMixin.class);
sourceMixins.put(OAuthFlows.class, ExtensionsMixin.class);
sourceMixins.put(OpenAPI.class, OpenAPIMixin.class);
sourceMixins.put(Operation.class, OperationMixin.class);
sourceMixins.put(Parameter.class, ExtensionsMixin.class);
sourceMixins.put(PathItem.class, ExtensionsMixin.class);
sourceMixins.put(Paths.class, ExtensionsMixin.class);
sourceMixins.put(RequestBody.class, ExtensionsMixin.class);
sourceMixins.put(Scopes.class, ExtensionsMixin.class);
sourceMixins.put(SecurityScheme.class, ExtensionsMixin.class);
sourceMixins.put(Server.class, ExtensionsMixin.class);
sourceMixins.put(ServerVariable.class, ExtensionsMixin.class);
sourceMixins.put(ServerVariables.class, ExtensionsMixin.class);
sourceMixins.put(Tag.class, ExtensionsMixin.class);
sourceMixins.put(XML.class, ExtensionsMixin.class);
sourceMixins.put(Schema.class, ExtensionsMixin.class);
sourceMixins.put(DateSchema.class, DateSchemaMixin.class);

mapper.setMixIns(sourceMixins);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.callbacks.Callback;
import io.swagger.v3.oas.annotations.enums.Explode;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.models.Components;
Expand Down Expand Up @@ -374,7 +375,7 @@ public void visitMethod(MethodElement element, VisitorContext context) {
} else if (parameter.isAnnotationPresent(QueryValue.class)) {
paramValues.put("in", ParameterIn.QUERY.toString());
}

processExplode(paramAnn, paramValues);

JsonNode jsonNode = jsonMapper.valueToTree(paramValues);

Expand Down Expand Up @@ -509,6 +510,40 @@ public void visitMethod(MethodElement element, VisitorContext context) {
});
}

private void processExplode(AnnotationValue<io.swagger.v3.oas.annotations.Parameter> paramAnn, Map<CharSequence, Object> paramValues) {
Optional<Explode> explode = paramAnn.enumValue("explode", Explode.class);
if (explode.isPresent()) {
Explode ex = explode.get();
switch (ex) {
case TRUE:
paramValues.put("explode", Boolean.TRUE);
break;
case FALSE:
paramValues.put("explode", Boolean.FALSE);
break;
case DEFAULT:
default:
String in = (String) paramValues.get("in");
if (in == null || in.isEmpty()) {
in = "DEFAULT";
}
switch (ParameterIn.valueOf(in.toUpperCase(Locale.US))) {
case COOKIE:
case QUERY:
paramValues.put("explode", Boolean.TRUE);
break;
case DEFAULT:
case HEADER:
case PATH:
default:
paramValues.put("explode", Boolean.FALSE);
break;
}
break;
}
}
}

private boolean isIgnoredParameterType(ClassElement parameterType) {
return parameterType == null ||
parameterType.isAssignable(Principal.class) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
package io.micronaut.openapi.visitor

import io.micronaut.annotation.processing.test.AbstractTypeElementSpec
import io.swagger.v3.oas.annotations.enums.ParameterIn
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.PathItem
import io.swagger.v3.oas.models.media.Schema
import io.swagger.v3.oas.models.parameters.HeaderParameter

class OpenApiParameterMappingSpec extends AbstractTypeElementSpec {

Expand Down Expand Up @@ -453,6 +455,96 @@ class MyBean {}
pathItem.get.parameters[1].name == 'name'
pathItem.get.parameters[1].in == 'query'
}

void "test @Parameter in header and explode is true"() {

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.*;
/**
* @author graemerocher
* @since 1.0
*/

@Controller("/networks")
interface NetworkOperations {

/**
* @param fooBar some other description
*/
@Operation(
summary = "Gets mappings from TTT using vod provider mappings",
description = "Migration of /networks endpoint from TTT. Gets mappings from XYZ using provider mappings",
responses = {
@ApiResponse(
responseCode = "200", description = "Successfully got abc data from TTT",
content = {
@Content(
mediaType = "application/json", schema = @Schema(implementation = Greeting.class)
)
}
)
})
@Get
public HttpResponse<Greeting> getNetworks(
@Parameter(
in = ParameterIn.HEADER,
name = "fooBar",
description = "NA/true/false (case insensitive)",
required = false,
explode = Explode.TRUE,
schema = @Schema(
implementation = Greeting.class
)
)
Greeting fooBar
);
}

class Greeting {
public String message;
}
@javax.inject.Singleton
class MyBean {}
''')
then:"the state is correct"
AbstractOpenApiVisitor.testReference != null

when:"The OpenAPI is retrieved"
OpenAPI openAPI = AbstractOpenApiVisitor.testReference
Schema greetingSchema = openAPI.components.schemas['Greeting']

then:"the components are valid"
greetingSchema.type == 'object'
greetingSchema.properties.size() == 1
greetingSchema.properties['message'].type == 'string'

when:"the /pets path is retrieved"
PathItem pathItem = openAPI.paths.get("/networks")

then:"it is included in the OpenAPI doc"
pathItem.get.operationId == 'getNetworks'
pathItem.get.parameters.size() == 1
pathItem.get.parameters[0].name =='fooBar'
pathItem.get.parameters[0].class == HeaderParameter
pathItem.get.parameters[0].explode
pathItem.get.parameters[0].description == 'NA/true/false (case insensitive)'
!pathItem.get.parameters[0].required
pathItem.get.parameters[0].schema.$ref == '#/components/schemas/Greeting'
}
}