diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java index ad78f6d84..0f7c7c960 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestBuilder.java @@ -174,7 +174,10 @@ public Operation build(HandlerMethod handlerMethod, RequestMethod requestMethod, for (MethodParameter methodParameter : parameters) { // check if query param Parameter parameter = null; - io.swagger.v3.oas.annotations.Parameter parameterDoc = methodParameter.getParameterAnnotation(io.swagger.v3.oas.annotations.Parameter.class); + io.swagger.v3.oas.annotations.Parameter parameterDoc = AnnotatedElementUtils.findMergedAnnotation( + AnnotatedElementUtils.forAnnotations(methodParameter.getParameterAnnotations()), + io.swagger.v3.oas.annotations.Parameter.class); + final String pName = methodParameter.getParameterName(); ParameterInfo parameterInfo = new ParameterInfo(pName, methodParameter); diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/AccountId.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/AccountId.java new file mode 100644 index 000000000..b4a8d4d0f --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/AccountId.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ +package test.org.springdoc.api.app120; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.core.annotation.AliasFor; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +@Target({PARAMETER, METHOD, ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Parameter(description = "non alias description") +public @interface AccountId { + + @AliasFor(annotation = Parameter.class, value = "name") + String name() default ""; + + @AliasFor(annotation = Parameter.class, value = "example") + String example() default "123456"; + + @AliasFor(annotation = Parameter.class, value = "in") + ParameterIn in() default ParameterIn.DEFAULT; + + @AliasFor(annotation = Parameter.class, value = "schema") + Schema schema() default @Schema(); +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/MetaAnnotationController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/MetaAnnotationController.java new file mode 100644 index 000000000..0002ef726 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/MetaAnnotationController.java @@ -0,0 +1,163 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ +package test.org.springdoc.api.app120; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.Explode; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.enums.ParameterStyle; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +@RestController +public class MetaAnnotationController { + + @GetMapping(value = "/simpleTest/{accountId}") + String simpleTest(@PathVariable @AccountId String accountId) { + return accountId; + } + + /** + * When there is a top level {@code @Parameter} annotation it has precedence over the meta-annotation + * So the id parameter should have all the defaults, with a name of "id" + */ + @GetMapping(value = "/testTopLevelParamAnnotationOverrides/{accountId}") + String testTopLevelParamAnnotationOverrides(@PathVariable @AccountId @Parameter(name = "id") String accountId) { + return accountId; + } + + @GetMapping(value = "/testQueryParam") + String testQueryParam(@RequestParam @AccountId String accountId) { + return accountId; + } + + /** + * {@code @AliasFor} in the {@code @AccountId} annotation allows us to override the default it provides. + */ + @GetMapping(value = "/testAliasFor") + String testAliasFor(@RequestParam @AccountId(example = "OVERRIDDEN EXAMPLE") String accountId) { + return accountId; + } + + /** + * This should inherent all the attributes of {@code @AccountId}, but give it a different name + */ + @Target({PARAMETER, METHOD, ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @AccountId(name = "queryAccountId") + @interface QueryAccountId { + } + + @GetMapping(value = "/testMetaMetaAnnotation/{accountId}") + String testMetaMetaAnnotation( + @RequestParam @QueryAccountId String queryAccountId, + @PathVariable @AccountId String accountId) { + return accountId; + } + + @Target({PARAMETER, METHOD, ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Parameter + @interface TestAllAttributesAsAlias { + + @AliasFor(annotation = Parameter.class, attribute="name") + String name() default "name"; + + @AliasFor(annotation = Parameter.class, attribute="in") + ParameterIn in() default ParameterIn.QUERY; + + @AliasFor(annotation = Parameter.class, attribute="description") + String description() default "desc"; + + @AliasFor(annotation = Parameter.class, attribute="required") + boolean required() default true; + + @AliasFor(annotation = Parameter.class, attribute="deprecated") + boolean deprecated() default true; + + @AliasFor(annotation = Parameter.class, attribute="allowEmptyValue") + boolean allowEmptyValue() default true; + + @AliasFor(annotation = Parameter.class, attribute="style") + ParameterStyle style() default ParameterStyle.DEEPOBJECT; + + @AliasFor(annotation = Parameter.class, attribute="explode") + Explode explode() default Explode.TRUE; + + @AliasFor(annotation = Parameter.class, attribute="allowReserved") + boolean allowReserved() default true; + + @AliasFor(annotation = Parameter.class, attribute="schema") + Schema schema() default @Schema(name = "special schema", implementation = Boolean.class); + + @AliasFor(annotation = Parameter.class, attribute="array") + ArraySchema array() default @ArraySchema(); + + @AliasFor(annotation = Parameter.class, attribute="content") + Content[] content() default {}; + + @AliasFor(annotation = Parameter.class, attribute="hidden") + boolean hidden() default false; + + @AliasFor(annotation = Parameter.class, attribute="examples") + ExampleObject[] examples() default {}; + + @AliasFor(annotation = Parameter.class, attribute="example") + String example() default "1234"; + + @AliasFor(annotation = Parameter.class, attribute="extensions") + Extension[] extensions() default {}; + + @AliasFor(annotation = Parameter.class, attribute="ref") + String ref() default ""; + } + + @GetMapping(value = "/testAllAttributesAsAlias/") + String testAllAttributesAsAlias( + @RequestParam @TestAllAttributesAsAlias String name) { + return name; + } + + @Target({PARAMETER, METHOD, ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Parameter(name = "name", description = "desc", schema = @Schema(implementation = Boolean.class)) + @interface TestNoAliasFors { + } + + @GetMapping(value = "/testNoAliasFors/") + String testNoAliasFors( + @RequestParam @TestAllAttributesAsAlias String name) { + return name; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/SpringDocApp120Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/SpringDocApp120Test.java new file mode 100644 index 000000000..28bc22f97 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app120/SpringDocApp120Test.java @@ -0,0 +1,31 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ +package test.org.springdoc.api.app120; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import test.org.springdoc.api.AbstractSpringDocTest; + + +/** + * Tests Spring meta-annotations as method parameters + */ +public class SpringDocApp120Test extends AbstractSpringDocTest { + + @SpringBootApplication + static class SpringDocTestApp {} +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app120.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app120.json new file mode 100644 index 000000000..565fe81d0 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app120.json @@ -0,0 +1,258 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/simpleTest/{accountId}": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "simpleTest", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "non alias description", + "required": true, + "schema": { + "type": "string" + }, + "example": 123456 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/testTopLevelParamAnnotationOverrides/{accountId}": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "testTopLevelParamAnnotationOverrides", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/testQueryParam": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "testQueryParam", + "parameters": [ + { + "name": "accountId", + "in": "query", + "description": "non alias description", + "required": true, + "schema": { + "type": "string" + }, + "example": 123456 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/testAliasFor": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "testAliasFor", + "parameters": [ + { + "name": "accountId", + "in": "query", + "description": "non alias description", + "required": true, + "schema": { + "type": "string" + }, + "example": "OVERRIDDEN EXAMPLE" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/testMetaMetaAnnotation/{accountId}": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "testMetaMetaAnnotation", + "parameters": [ + { + "name": "queryAccountId", + "in": "query", + "description": "non alias description", + "required": true, + "schema": { + "type": "string" + }, + "example": 123456 + }, + { + "name": "accountId", + "in": "path", + "description": "non alias description", + "required": true, + "schema": { + "type": "string" + }, + "example": 123456 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/testAllAttributesAsAlias/": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "testAllAttributesAsAlias", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "desc", + "required": true, + "deprecated": true, + "allowEmptyValue": true, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "type": "boolean" + }, + "example": 1234 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/testNoAliasFors/": { + "get": { + "tags": [ + "meta-annotation-controller" + ], + "operationId": "testNoAliasFors", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "desc", + "required": true, + "deprecated": true, + "allowEmptyValue": true, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "type": "boolean" + }, + "example": 1234 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": {} +}