From 1a90a37a0450a0501bbd09b8785c49fa27cdd325 Mon Sep 17 00:00:00 2001 From: "Badr.NassLahsen" Date: Tue, 12 Mar 2024 18:50:21 +0100 Subject: [PATCH] releasing v1.8.0 --- CHANGELOG.md | 84 +++- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.adoc | 4 +- README.md | 17 +- pom.xml | 21 +- springdoc-openapi-common/pom.xml | 2 +- .../api/AbstractOpenApiResource.java | 149 +++--- .../core/AbstractRequestService.java | 80 +++- .../AbstractSwaggerUiConfigProperties.java | 4 +- .../java/org/springdoc/core/Constants.java | 25 +- .../springdoc/core/ControllerAdviceInfo.java | 46 +- .../core/DelegatingMethodParameter.java | 15 +- .../core/GenericParameterService.java | 75 +-- .../core/GenericResponseService.java | 333 ++++++++------ .../org/springdoc/core/GroupedOpenApi.java | 6 +- .../org/springdoc/core/MethodAdviceInfo.java | 106 +++++ .../core/MethodParameterPojoExtractor.java | 6 +- .../org/springdoc/core/OpenAPIService.java | 16 +- .../org/springdoc/core/OperationService.java | 73 +-- .../springdoc/core/PropertyResolverUtils.java | 65 +++ .../springdoc/core/RequestBodyService.java | 57 ++- .../org/springdoc/core/ReturnTypeParser.java | 30 +- .../org/springdoc/core/SecurityService.java | 62 ++- .../core/SpecPropertiesCondition.java | 59 +++ .../core/SpringDocAnnotationsUtils.java | 194 ++++---- .../core/SpringDocConfigProperties.java | 404 +++++++++++++---- .../core/SpringDocConfiguration.java | 48 +- .../SpringDocSpecPropertiesConfiguration.java | 114 +++++ .../core/SpringDocUIConfiguration.java | 2 - .../org/springdoc/core/SpringDocUtils.java | 13 + ...pringdocActuatorBeanFactoryConfigurer.java | 12 +- .../core/SwaggerUiConfigParameters.java | 6 +- .../converters/ModelConverterRegistrar.java | 9 +- .../core/converters/models/SortObject.java | 67 +++ .../ActuatorOperationCustomizer.java | 44 +- ...stDelegatingMethodParameterCustomizer.java | 426 ++++++++++++++++-- .../customizers/SpecPropertiesCustomizer.java | 211 +++++++++ ...ecificationStringPropertiesCustomizer.java | 171 +++++++ .../springdoc/core/fn/RouterFunctionData.java | 3 + .../core/fn/builders/arrayschema/Builder.java | 86 +++- .../core/fn/builders/content/Builder.java | 144 +++++- .../discriminatormapping/Builder.java | 34 +- .../core/fn/builders/requestbody/Builder.java | 31 +- .../core/fn/builders/schema/Builder.java | 387 +++++++++++++--- .../SpringCloudFunctionProvider.java | 2 +- .../WebConversionServiceProvider.java | 25 +- .../ui/AbstractSwaggerIndexTransformer.java | 13 + .../ui/AbstractSwaggerResourceResolver.java | 53 +-- .../springdoc/ui/AbstractSwaggerWelcome.java | 5 +- .../main/resources/META-INF/spring.factories | 1 + .../AbstractSwaggerIndexTransformerTest.java | 69 +++ .../AbstractSwaggerResourceResolverTest.java | 51 +++ springdoc-openapi-data-rest/pom.xml | 2 +- .../rest/SpringDocDataRestConfiguration.java | 8 +- .../SpringRepositoryRestResourceProvider.java | 2 +- .../rest/core/DataRestOperationService.java | 2 +- .../rest/core/DataRestRequestService.java | 2 +- .../QuerydslPredicateOperationCustomizer.java | 69 +-- .../rest/utils/SpringDocDataRestUtils.java | 181 ++++---- .../api/app37/ProductRepository.java | 3 +- .../src/test/resources/results/app24.json | 54 ++- .../src/test/resources/results/app27.json | 31 +- .../src/test/resources/results/app37.json | 2 +- springdoc-openapi-groovy/pom.xml | 2 +- .../springdoc/api/app1/CarController.groovy | 7 +- .../src/test/resources/results/app1.json | 38 +- springdoc-openapi-hateoas/pom.xml | 2 +- .../SpringDocHateoasConfiguration.java | 5 +- .../OpenApiHateoasLinksCustomiser.java | 2 +- springdoc-openapi-javadoc/pom.xml | 2 +- .../javadoc/JavadocPropertyCustomizer.java | 99 +++- .../api/app110/PersonController.java | 5 +- .../api/app110/PersonController2.java | 5 +- .../api/app112/sample/PersonController2.java | 5 +- .../org/springdoc/api/app13/PersonDTO.java | 4 + .../test/org/springdoc/api/app170/Animal.java | 17 + .../springdoc/api/app170/BasicController.java | 19 + .../test/org/springdoc/api/app170/Cat.java | 22 + .../test/org/springdoc/api/app170/Dog.java | 33 ++ .../api/app170/SpringDocApp170Test.java | 37 ++ .../springdoc/api/app171/HelloController.java | 40 ++ .../org/springdoc/api/app171/PersonDTO.java | 73 +++ .../api/app171/PersonProjection.java | 42 ++ .../api/app171/SpringDocApp171Test.java | 37 ++ .../JavadocPropertyCustomizerTest.java | 16 +- .../org/springdoc/api/app173/Example.java | 21 + .../api/app173/ExampleController.java | 39 ++ .../api/app173/SpringDocApp173Test.java | 21 + .../src/test/resources/results/app104.json | 3 +- .../src/test/resources/results/app105-3.json | 8 +- .../src/test/resources/results/app114.json | 2 +- .../src/test/resources/results/app126.json | 18 +- .../src/test/resources/results/app13.json | 6 +- .../src/test/resources/results/app170.json | 90 ++++ .../src/test/resources/results/app171.json | 65 +++ .../src/test/resources/results/app173.json | 132 ++++++ .../src/test/resources/results/app2.json | 8 +- .../src/test/resources/results/app87.json | 3 +- .../src/test/resources/results/app96.json | 19 +- springdoc-openapi-kotlin/pom.xml | 2 +- springdoc-openapi-security/pom.xml | 2 +- springdoc-openapi-ui/pom.xml | 2 +- .../ui/SwaggerIndexPageTransformer.java | 3 +- .../springdoc/webmvc/ui/SwaggerUiHome.java | 4 +- .../webmvc/ui/SwaggerWelcomeActuator.java | 6 +- .../webmvc/ui/SwaggerWelcomeWebMvc.java | 10 +- .../ui/app1/SpringDocSwaggerConfigTest.java | 2 + ...rConfigWithBothFileGeneratedSpecsTest.java | 67 +++ .../springdoc/ui/app33/HelloController.java | 37 ++ .../ui/app33/SpringDocApp33Test.java | 49 ++ .../src/test/resources/results/app33 | 24 + springdoc-openapi-webflux-core/pom.xml | 2 +- .../webflux/api/OpenApiActuatorResource.java | 3 + .../webflux/api/OpenApiWebfluxResource.java | 3 + .../core/SpringDocWebFluxConfiguration.java | 2 + .../core/fn/SpringdocRouteBuilder.java | 12 +- .../api/app145/SpringDocApp1451Test.java | 2 + .../api/app190/SpringDocApp190Test.java | 44 ++ .../api/app190/SpringDocApp190bisTest.java | 43 ++ springdoc-openapi-webflux-ui/pom.xml | 2 +- .../ui/SwaggerIndexPageTransformer.java | 4 +- .../webflux/ui/SwaggerResourceResolver.java | 36 +- .../webflux/ui/SwaggerWelcomeActuator.java | 10 +- .../webflux/ui/SwaggerWelcomeCommon.java | 5 - .../webflux/ui/SwaggerWelcomeWebFlux.java | 8 +- springdoc-openapi-webmvc-core/pom.xml | 2 +- .../webmvc/api/OpenApiActuatorResource.java | 3 + .../webmvc/api/OpenApiWebMvcResource.java | 3 + .../core/SpringDocWebMvcConfiguration.java | 2 + .../webmvc/core/fn/SpringdocRouteBuilder.java | 12 +- .../api/v30/app110/PersonController.java | 5 +- .../v30/app112/sample/PersonController2.java | 5 +- .../api/v30/app138/SpringDocApp138Test.java | 2 +- .../api/v30/app173/SpringDocApp173Test.java | 5 +- .../api/v30/app188/HelloController.java | 3 +- .../api/v30/app197/Example2Controller.java | 3 +- .../api/v30/app197/ExampleController.java | 3 +- .../api/v30/app199/HelloController.java | 5 +- .../api/v30/app202/Example2Controller.java | 3 +- .../api/v30/app202/ExampleController.java | 3 +- .../api/v30/app206/PersonRequest.java | 55 +++ .../api/v30/app206/PersonResponse.java | 55 +++ .../v30/app206/RequiredModeController.java | 14 + .../api/v30/app206/SpringdocApp206Test.java | 12 + .../api/v30/app207/SpringdocApp207Test.java | 12 + .../app207/controller/SuperController.java | 20 + .../app207/controller/SysUserController.java | 14 + .../api/v30/app207/model/SuperEntity.java | 5 + .../api/v30/app207/model/SysUser.java | 4 + .../api/v30/app208/HelloController.java | 26 ++ .../api/v30/app208/RequestObject.java | 26 ++ .../api/v30/app208/SpringdocApp208Test.java | 14 + .../api/v30/app209/HelloController.java | 71 +++ .../api/v30/app209/SpringDocApp209Test.java | 114 +++++ .../api/v30/app210/SpringDocApp210Test.java | 47 ++ .../api/v30/app211/HelloController.java | 101 +++++ .../api/v30/app211/SpringDocApp211Test.java | 36 ++ .../api/v30/app212/HelloController.java | 32 ++ .../springdoc/api/v30/app212/PersonDTO.java | 31 ++ .../api/v30/app212/SpringDocApp212Test.java | 66 +++ .../api/v30/app213/HelloController.java | 67 +++ .../springdoc/api/v30/app213/PersonDTO.java | 113 +++++ .../api/v30/app213/SpringDocApp213Test.java | 39 ++ .../api/v30/app215/SpringDocApp215Test.java | 44 ++ .../v30/app215/SpringDocApp215bisTest.java | 44 ++ .../api/v30/app216/HelloController.java | 47 ++ .../api/v30/app216/SpringDocApp216Test.java | 37 ++ .../api/v31/app7/ExamplesController.java | 35 ++ .../api/v31/app7/ExamplesResponse.java | 29 ++ .../api/v31/app7/SpringDocApp7Test.java | 35 ++ .../api/v31/app8/ExamplesController.java | 36 ++ .../api/v31/app8/ExamplesResponse.java | 49 ++ .../org/springdoc/api/v31/app8/FooBar.java | 32 ++ .../api/v31/app8/SpringDocApp8Test.java | 35 ++ .../org/springdoc/api/v31/app8/UserInfo.java | 45 ++ .../src/test/resources/application-212.yml | 35 ++ .../src/test/resources/application-213.yml | 26 ++ .../test/resources/results/3.0.1/app173.json | 3 +- .../test/resources/results/3.0.1/app206.json | 113 +++++ .../test/resources/results/3.0.1/app207.json | 109 +++++ .../test/resources/results/3.0.1/app208.json | 96 ++++ .../test/resources/results/3.0.1/app209.json | 203 +++++++++ .../test/resources/results/3.0.1/app211.json | 79 ++++ .../results/3.0.1/app212-grouped.json | 53 +++ .../test/resources/results/3.0.1/app212.json | 54 +++ .../test/resources/results/3.0.1/app213.json | 79 ++++ .../test/resources/results/3.0.1/app216.json | 42 ++ .../test/resources/results/3.0.1/app96.json | 34 +- .../test/resources/results/3.1.0/app1.json | 2 - .../test/resources/results/3.1.0/app2.json | 16 - .../test/resources/results/3.1.0/app7.json | 63 +++ .../test/resources/results/3.1.0/app8.json | 111 +++++ 192 files changed, 7108 insertions(+), 1160 deletions(-) create mode 100644 springdoc-openapi-common/src/main/java/org/springdoc/core/MethodAdviceInfo.java create mode 100644 springdoc-openapi-common/src/main/java/org/springdoc/core/SpecPropertiesCondition.java create mode 100644 springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocSpecPropertiesConfiguration.java create mode 100644 springdoc-openapi-common/src/main/java/org/springdoc/core/converters/models/SortObject.java create mode 100644 springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecPropertiesCustomizer.java create mode 100644 springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java create mode 100644 springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java create mode 100644 springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerResourceResolverTest.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Animal.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/BasicController.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Cat.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Dog.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/SpringDocApp170Test.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/HelloController.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonDTO.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonProjection.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java rename springdoc-openapi-javadoc/src/test/java/{org/springdoc/openapi/javadoc => test/org/springdoc/api/app172}/JavadocPropertyCustomizerTest.java (88%) create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/Example.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/ExampleController.java create mode 100644 springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java create mode 100644 springdoc-openapi-javadoc/src/test/resources/results/app170.json create mode 100644 springdoc-openapi-javadoc/src/test/resources/results/app171.json create mode 100644 springdoc-openapi-javadoc/src/test/resources/results/app173.json create mode 100644 springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigWithBothFileGeneratedSpecsTest.java create mode 100644 springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/HelloController.java create mode 100644 springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocApp33Test.java create mode 100644 springdoc-openapi-ui/src/test/resources/results/app33 create mode 100644 springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190Test.java create mode 100644 springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190bisTest.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonRequest.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonResponse.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/RequiredModeController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/SpringdocApp206Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/SpringdocApp207Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SuperController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SysUserController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SuperEntity.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SysUser.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/HelloController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/RequestObject.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/SpringdocApp208Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/HelloController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/SpringDocApp209Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app210/SpringDocApp210Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/HelloController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/PersonDTO.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/SpringDocApp212Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/HelloController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/PersonDTO.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/SpringDocApp213Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215bisTest.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/HelloController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/SpringDocApp216Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesResponse.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/SpringDocApp7Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesController.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesResponse.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/FooBar.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/SpringDocApp8Test.java create mode 100644 springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/UserInfo.java create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/application-212.yml create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/application-213.yml create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app206.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app207.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app208.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app209.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app211.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212-grouped.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app213.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app216.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app7.json create mode 100644 springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app8.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 04849b814..cdfef5fed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,78 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.8.0] - 2024-03-12 + +### Added + +- #2189 - Add support for swagger-ui.url property +- #2200 - Support schema.requiredMode() on ParameterObject +- #2309 - Added function to preload by specifying locale +- #2332 - Group name cannot be null or empty +- #2281 - Initial Virtual thread support +- #2311 - Enhance springdoc-ui to support spring.mvc.servlet.path +- #2340 - Add support OIDC with Spring Authorization Server +- #2345 - Support Schema added in OpenAPI Specification v3.1 +- #2387 - Support get javadoc description from getter method +- #2404 - Update condition to register links schema customizer +- #2359 - Update condition to register links schema customizer +- #2348 - Enhance resource path processing +- #2438, #2315 - Support for @JsonProperty with Javadoc Change in springdoc-openapi +- #2443 - Respect schema annotations when using spring mvc with kotlin +- #2492, #2488 - Support dynamic evaluation of description field in the RequestBody +- #2510 - Option to disable root api-docs path when using groups + +### Changed + +- Upgrade spring-boot to 2.7.14 +- Upgrade swagger-core to 2.2.20 +- Upgrade swagger-ui to 5.11.8 + +### Fixed + +- #2199 - Fix Schema get condition of ArraySchema. +- #2194 - Fix Swagger UI with provided spec +- #2213 - Using both generated and configured specs stoped working in 1.6.5 +- #2222 - String Index Out of Bounce Exception Fix when deployed on Azure +- #2243, #2235 - Fix StringIndexOutOfBoundsException when path is same webjar +- #2291 - Fix default-flat-param-object doesn't work when using http body +- #2310 - Change bean name of objectMapperProvider +- #2207 - swagger-initializer.js is sent endcoded in the JVM's default charset +- #2271, #2280 - Fix loop when response inherits generic class fixes +- #2239 - Swagger UI not accessible when FormattingConversionService is a CGLIB proxy +- #2366 - Fix the failed test due to hardcoded file separators +- #2370, #2371 - No empty description for polymorphic subtypes +- #2373 - SchemaProperty.array Schema is ignored in /api-docs or api-docs.yaml +- #2366 - Refactoring AbstractSwaggerResourceResolver.findWebJarResourcePath +- #2320 - javadoc for class attribute ignored when in EntityModel. +- #2347 - Not working if a property of entity contains generic parameters. +- #2399 - SpringdocRouteBuilder.onError is overriding last route defined. +- #2426 - StackOverflowError when using @ParameterObject on groovy class. +- #2453 - Fix CODE_OF_CONDUCT.md links +- #2454 - Fix typo in SwaggerWelcomeWebMvc +- #2507 - Fix typo in Constants +- #2472 - Update JavadocPropertyCustomizer.java +- #2495 - Fix broken links in README and CONTRIBUTING +- #2501 - bug fix when "exported" is set to false in RestResource annotation +- #2447 - Serialization to openapi of org.springframework.data.domain.Sort is not done correctly +- #2449 - Extensions in subobjects of OpenAPI no longer work +- #2461 - Springdoc OpenApi Annotations @ExtensionProperty Not Evaluating Properties from application.yml +- #2469 - Pom contains invalid organizationUrl +- #2518 - Duplicate GroupConfigs in SpringDocConfigProperties +- #2506 - Springdoc breaks (Unexpected value: TRACE) when a spring-cloud-starter-gateway-mvc universal gateway is configured. +- #2519 - Request parameter parsing error after using @NotBlank from type interface field +- #2516 - Spring Data REST fails when setting version to openapi_3_1 +- #2509 - ArrayIndexOutOfBoundsException in SwaggerUiConfigParameters +- #2484 - JavaDoc integration not working with SnakeCaseStrategy property naming +- #2483 - Controller advice documents ApiResponse on every operation, even if the operation does not annotate the exception to be thrown +- #2477 - buildApiResponses ignores produced ContentType in case of many @Operation + ## [1.7.0] - 2023-04-01 ### Added - #2152 - Detect directions in default sort values -- #2167 #2166 - Add request parameter for token endpoint +- #2167 #2166 - Add request parameter for token endpoint - #2188 - Support of {*param} path patterns ### Changed @@ -19,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade swagger-core to 2.2.9 - Upgrade swagger-ui to 4.18.2 - Spring Native is now superseded by Spring Boot 3 official -- #2173 - Remove webjars-locator-core +- #2173 - Remove webjars-locator-core ### Fixed @@ -28,8 +94,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #2140 - Javadoc record class parameters not recognized - #2123 #2141 - fix spring authorization server response. - #2148 - Fix properties show-oauth2-endpoints and SpringDocConfigProperties#showOauth2Endpoint properties name mismatch -- #2149 - Request parameters with default values are marked as required. -- #2155 - openApi.getServers() is null in OpenApiCustomiser when using different locales. +- #2149 - Request parameters with default values are marked as required. +- #2155 - openApi.getServers() is null in OpenApiCustomiser when using different locales. - #2152 - Redundant(wrong) direction appended to @PageableDefault. - #2181 #2183 - Fixed DefaultFlatParamObject to work with annotated parameters. - #2170 #2187 - All request parameters marked as required for Java controllers in mixed projects in 2.0.3 @@ -42,8 +108,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #2006 - Support for nullable request parameters in Kotlin. - #2054 - Add copyright and license information to Jar. -- #2021 - Required field in Schema annotation ignored in Kotlin. -- #2094 - Initial support for Spring Authorization Server. +- #2021 - Required field in Schema annotation ignored in Kotlin. +- #2094 - Initial support for Spring Authorization Server. ### Changed - Upgrade spring-boot to 2.7.9 @@ -52,7 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- #2010 - findByNameContainingIgnoreCaseAndDateBefore throw NullPointerException. +- #2010 - findByNameContainingIgnoreCaseAndDateBefore throw NullPointerException. - #2031 - Path variables parameters are not assigned correctly to endpoints. - #2038 - When extends JpaRepository, using @Parameter over the method results in duplicate of the same parameter. - #2046 - Map Fields Disappear with Groovy on Classpath. @@ -99,8 +165,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - #1892 - springdoc.model-and-view-allowed enhanced -- #1901 - When @Get, using @Parameter over the method results in duplicate of the same parameter -- #1909 - ExceptionHandler in controller is not used by another controller +- #1901 - When @Get, using @Parameter over the method results in duplicate of the same parameter +- #1909 - ExceptionHandler in controller is not used by another controller - #1904 - springdoc-openapi-webflux-ui 2.0.0-M7 + spring actuator + spring cloud crashes at startup - #1911 - Wrong type for springdoc.swagger-ui.oauth.useBasicAuthenticationWithAccessCodeGrant configuration property - #1931 - Spring Security form login only offers application/json req body type. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 95b91f24b..f7dce2d75 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ -## Contributor Code of Conduct += Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index df1a65722..32b6f3756 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -79,7 +79,7 @@ If you have performed a checkout of this repository already, use "`File`" -> "`O Alternatively, you can let IntellIJ IDEA checkout the code for you. Use "`File`" -> "`New`" -> "`Project from Version Control`" and -`https://github.com/springdoc/springdoc-openapi.git` for the URL. +`https://github.com/springdoc/springdoc-openapi-v1.git` for the URL. Once the checkout has completed, a pop-up will suggest to open the project. ==== Install the Spring Formatter plugin @@ -132,5 +132,5 @@ If you get `Filename too long` errors, set the `core.longPaths=true` git option: ``` -git clone -c core.longPaths=true https://github.com/springdoc/springdoc-openapi +git clone -c core.longPaths=true https://github.com/springdoc/springdoc-openapi-v1 ``` diff --git a/README.md b/README.md index 437dd1635..1b0c576c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Octocat](https://springdoc.org/img/banner-logo.svg) -[![Build Status](http://129.159.254.115:8686/buildStatus/icon?job=springdoc-openapi-IC)](http://129.159.254.115:8686/view/springdoc-openapi/job/springdoc-openapi-IC/) -[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=springdoc_springdoc-openapi&metric=alert_status)](https://sonarcloud.io/dashboard?id=springdoc_springdoc-openapi) +[![Build Status](https://ci-cd.springdoc.org:8443/buildStatus/icon?job=springdoc-openapi-IC)](https://ci-cd.springdoc.org:8443/view/springdoc-openapi/job/springdoc-openapi-IC/) +[![Quality Gate Status](https://sonar.springdoc.org/api/project_badges/measure?project=springdoc-openapi&metric=alert_status&token=sqb_b0202ce5e7c6d83a91e0bcb04d1cf24419585e34)](https://sonar.springdoc.org/dashboard?id=springdoc-openapi) [![Known Vulnerabilities](https://snyk.io/test/github/springdoc/springdoc-openapi.git/badge.svg)](https://snyk.io/test/github/springdoc/springdoc-openapi.git) [![Stack Exchange questions](https://img.shields.io/stackexchange/stackoverflow/t/springdoc)](https://stackoverflow.com/questions/tagged/springdoc?tab=Votes) @@ -17,9 +17,6 @@ This project is sponsored by    - - - @@ -111,7 +108,7 @@ springdoc.swagger-ui.path=/swagger-ui.html ## Spring-boot with OpenAPI Demo applications. -### [Source Code for Demo Applications](https://github.com/springdoc/springdoc-openapi-demos.git). +### [Source Code for Demo Applications](https://github.com/springdoc/springdoc-openapi-v1-demos.git). ### [Demo Spring Boot 2 Web MVC with OpenAPI 3](http://158.101.191.70:8081/). @@ -202,12 +199,12 @@ The artifacts can be viewed accessed at the following locations: Releases: -* [https://s01.oss.sonatype.org/content/groups/public/org/springdoc/](https://s01.oss.sonatype.org/content/groups/public/org/springdoc/) +* [https://nexus.springdoc.org/repository/maven-releases/org/springdoc/](https://nexus.springdoc.org/content/groups/public/org/springdoc/) . Snapshots: -* [https://s01.oss.sonatype.org/content/repositories/snapshots/org/springdoc/](https://s01.oss.sonatype.org/content/repositories/snapshots/org/springdoc/) +* [https://nexus.springdoc.orgg/repository/maven-snapshots/org/springdoc/](https://nexus.springdoc.org/content/repositories/snapshots/org/springdoc/) . # Acknowledgements @@ -215,9 +212,9 @@ Snapshots: ## Contributors springdoc-openapi is relevant and updated regularly due to the valuable contributions from -its [contributors](https://github.com/springdoc/springdoc-openapi/graphs/contributors). +its [contributors](https://github.com/springdoc/springdoc-openapi-v1/graphs/contributors). - + diff --git a/pom.xml b/pom.xml index bb4fdaa9e..1bedcb4a8 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 pom Spring openapi documentation Spring openapi documentation @@ -11,7 +11,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.10 + 2.7.18 @@ -26,8 +26,7 @@ Badr NASS LAHSEN support@springdoc.org springdoc - https://springdoc.github.io/springdoc-openapi/ - + https://springdoc.org/ @@ -36,7 +35,7 @@ scm:git:git@github.com:springdoc/springdoc-openapi.git scm:git:git@github.com:springdoc/springdoc-openapi.git - v1.7.0 + v1.8.0 @@ -67,8 +66,8 @@ 1.6 2.5.3 1.6.8 - 2.2.9 - 4.18.2 + 2.2.20 + 5.11.8 2.5.2.RELEASE 1.8.1 2.1 @@ -78,6 +77,7 @@ 3.2.4 8.2.1 0.4.1 + 9.0.86 @@ -193,6 +193,11 @@ springdoc-openapi-javadoc ${project.version} + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.embed.version} + @@ -384,4 +389,4 @@ - + \ No newline at end of file diff --git a/springdoc-openapi-common/pom.xml b/springdoc-openapi-common/pom.xml index 32ddaebaa..a00bcb5ed 100644 --- a/springdoc-openapi-common/pom.xml +++ b/springdoc-openapi-common/pom.xml @@ -3,7 +3,7 @@ org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 springdoc-openapi-common diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java index 3588a3627..e2005a094 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java @@ -46,6 +46,8 @@ import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executors; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonView; @@ -65,6 +67,7 @@ import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.PathItem.HttpMethod; import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.responses.ApiResponses; @@ -83,6 +86,7 @@ import org.springdoc.core.SpringDocConfigProperties.ApiDocs.OpenApiVersion; import org.springdoc.core.SpringDocConfigProperties.GroupConfig; import org.springdoc.core.SpringDocProviders; +import org.springdoc.core.SpringDocUtils; import org.springdoc.core.annotations.RouterOperations; import org.springdoc.core.customizers.DataRestRouterOperationCustomizer; import org.springdoc.core.customizers.OpenApiLocaleCustomizer; @@ -197,6 +201,11 @@ public abstract class AbstractOpenApiResource extends SpecFilter { */ protected final SpringDocCustomizers springDocCustomizers; + /** + * The Reentrant lock. + */ + private final Lock reentrantLock = new ReentrantLock(); + /** * Instantiates a new Abstract open api resource. * @@ -224,9 +233,15 @@ protected AbstractOpenApiResource(String groupName, ObjectFactory this.getOpenApi(Locale.forLanguageTag(locale))); + } + } + } } /** * Add rest controllers. @@ -308,72 +323,80 @@ private void getOpenApi() { * @param locale the locale * @return the open api */ - protected synchronized OpenAPI getOpenApi(Locale locale) { - final OpenAPI openAPI; - final Locale finalLocale = locale == null ? Locale.getDefault() : locale; - if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) { - Instant start = Instant.now(); - openAPI = openAPIService.build(finalLocale); - Map mappingsMap = openAPIService.getMappingsMap().entrySet().stream() - .filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(), - Hidden.class) == null)) - .filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getClass())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1)); - - Map findControllerAdvice = openAPIService.getControllerAdviceMap(); - if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion()) - openAPI.openapi(OpenApiVersion.OPENAPI_3_1.getVersion()); - if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) { - if (!CollectionUtils.isEmpty(mappingsMap)) - findControllerAdvice.putAll(mappingsMap); - responseBuilder.buildGenericResponse(openAPI.getComponents(), findControllerAdvice, finalLocale); - } - getPaths(mappingsMap, finalLocale, openAPI); + protected OpenAPI getOpenApi(Locale locale) { + this.reentrantLock.lock(); + try { + final OpenAPI openAPI; + final Locale finalLocale = locale == null ? Locale.getDefault() : locale; + if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) { + Instant start = Instant.now(); + openAPI = openAPIService.build(finalLocale); + Map mappingsMap = openAPIService.getMappingsMap().entrySet().stream() + .filter(controller -> (AnnotationUtils.findAnnotation(controller.getValue().getClass(), + Hidden.class) == null)) + .filter(controller -> !AbstractOpenApiResource.isHiddenRestControllers(controller.getValue().getClass())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1)); + + Map findControllerAdvice = openAPIService.getControllerAdviceMap(); + if (OpenApiVersion.OPENAPI_3_1 == springDocConfigProperties.getApiDocs().getVersion()){ + openAPI.openapi(OpenApiVersion.OPENAPI_3_1.getVersion()); + openAPI.specVersion(SpecVersion.V31); + } + if (springDocConfigProperties.isDefaultOverrideWithGenericResponse()) { + if (!CollectionUtils.isEmpty(mappingsMap)) + findControllerAdvice.putAll(mappingsMap); + responseBuilder.buildGenericResponse(openAPI.getComponents(), findControllerAdvice, finalLocale); + } + getPaths(mappingsMap, finalLocale, openAPI); - Optional cloudFunctionProviderOptional = springDocProviders.getSpringCloudFunctionProvider(); - cloudFunctionProviderOptional.ifPresent(cloudFunctionProvider -> { - List routerOperationList = cloudFunctionProvider.getRouterOperations(openAPI); - if (!CollectionUtils.isEmpty(routerOperationList)) - this.calculatePath(routerOperationList, locale, openAPI); - } - ); - if (!CollectionUtils.isEmpty(openAPI.getServers())) - openAPIService.setServersPresent(true); - else - openAPIService.setServersPresent(false); - openAPIService.updateServers(openAPI); + Optional cloudFunctionProviderOptional = springDocProviders.getSpringCloudFunctionProvider(); + cloudFunctionProviderOptional.ifPresent(cloudFunctionProvider -> { + List routerOperationList = cloudFunctionProvider.getRouterOperations(openAPI); + if (!CollectionUtils.isEmpty(routerOperationList)) + this.calculatePath(routerOperationList, locale, openAPI); + } + ); + if (!CollectionUtils.isEmpty(openAPI.getServers())) + openAPIService.setServersPresent(true); + else + openAPIService.setServersPresent(false); + openAPIService.updateServers(openAPI); + + if (springDocConfigProperties.isRemoveBrokenReferenceDefinitions()) + this.removeBrokenReferenceDefinitions(openAPI); + + // run the optional customisers + List servers = openAPI.getServers(); + List serversCopy = null; + try { + serversCopy = springDocProviders.jsonMapper() + .readValue(springDocProviders.jsonMapper().writeValueAsString(servers), new TypeReference>() {}); + } + catch (JsonProcessingException e) { + LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); + } - if (springDocConfigProperties.isRemoveBrokenReferenceDefinitions()) - this.removeBrokenReferenceDefinitions(openAPI); + openAPIService.getContext().getBeansOfType(OpenApiLocaleCustomizer.class).values().forEach(openApiLocaleCustomizer -> openApiLocaleCustomizer.customise(openAPI, finalLocale)); + springDocCustomizers.getOpenApiCustomizers().ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomiser -> openApiCustomiser.customise(openAPI))); + if (!CollectionUtils.isEmpty(openAPI.getServers()) && !openAPI.getServers().equals(serversCopy)) + openAPIService.setServersPresent(true); - // run the optional customisers - List servers = openAPI.getServers(); - List serversCopy = null; - try { - serversCopy = springDocProviders.jsonMapper() - .readValue(springDocProviders.jsonMapper().writeValueAsString(servers), new TypeReference>() {}); + openAPIService.setCachedOpenAPI(openAPI, finalLocale); + + LOGGER.info("Init duration for springdoc-openapi is: {} ms", + Duration.between(start, Instant.now()).toMillis()); } - catch (JsonProcessingException e) { - LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); + else { + LOGGER.debug("Fetching openApi document from cache"); + openAPI = openAPIService.getCachedOpenAPI(finalLocale); + openAPIService.updateServers(openAPI); } - openAPIService.getContext().getBeansOfType(OpenApiLocaleCustomizer.class).values().forEach(openApiLocaleCustomizer -> openApiLocaleCustomizer.customise(openAPI, finalLocale)); - springDocCustomizers.getOpenApiCustomizers().ifPresent(apiCustomisers -> apiCustomisers.forEach(openApiCustomiser -> openApiCustomiser.customise(openAPI))); - if (!CollectionUtils.isEmpty(openAPI.getServers()) && !openAPI.getServers().equals(serversCopy)) - openAPIService.setServersPresent(true); - - openAPIService.setCachedOpenAPI(openAPI, finalLocale); - - LOGGER.info("Init duration for springdoc-openapi is: {} ms", - Duration.between(start, Instant.now()).toMillis()); + return openAPI; } - else { - LOGGER.debug("Fetching openApi document from cache"); - openAPI = openAPIService.getCachedOpenAPI(finalLocale); - openAPIService.updateServers(openAPI); + finally { + this.reentrantLock.unlock(); } - - return openAPI; } /** @@ -466,7 +489,7 @@ protected void calculatePath(HandlerMethod handlerMethod, // RequestBody in Operation requestBuilder.getRequestBodyBuilder() .buildRequestBodyFromDoc(requestBodyDoc, methodAttributes, components, - methodAttributes.getJsonViewAnnotationForRequestBody()) + methodAttributes.getJsonViewAnnotationForRequestBody(), locale) .ifPresent(operation::setRequestBody); // requests operation = requestBuilder.build(handlerMethod, requestMethod, operation, methodAttributes, openAPI); @@ -1246,7 +1269,7 @@ protected URI getActuatorURI(String scheme, String host) { port = actuatorProvider.getApplicationPort(); path = actuatorProvider.getContextPath(); String mvcServletPath = this.openAPIService.getContext().getBean(Environment.class).getProperty(SPRING_MVC_SERVLET_PATH); - if (StringUtils.isNotEmpty(mvcServletPath)) + if (SpringDocUtils.isValidPath(mvcServletPath)) path = path + mvcServletPath; } try { diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java index 183ac04e5..5fe256ead 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractRequestService.java @@ -60,7 +60,6 @@ import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.parameters.RequestBody; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springdoc.core.SpringDocConfigProperties.ApiDocs.OpenApiVersion; import org.springdoc.core.customizers.ParameterCustomizer; @@ -70,7 +69,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.http.HttpMethod; -import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; @@ -87,6 +85,7 @@ import static org.springdoc.core.Constants.OPENAPI_ARRAY_TYPE; import static org.springdoc.core.Constants.OPENAPI_STRING_TYPE; +import static org.springdoc.core.GenericParameterService.isFile; import static org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.containsDeprecatedAnnotation; /** @@ -585,6 +584,24 @@ public void applyBeanValidatorAnnotations(final RequestBody requestBody, final L } } + /** + * Gets request body builder. + * + * @return the request body builder + */ + public RequestBodyService getRequestBodyBuilder() { + return requestBodyService; + } + + /** + * Is default flat param object boolean. + * + * @return the boolean + */ + public boolean isDefaultFlatParamObject() { + return defaultFlatParamObject; + } + /** * Calculate size. * @@ -605,15 +622,6 @@ else if (OPENAPI_STRING_TYPE.equals(schema.getType())) { } } - /** - * Gets request body builder. - * - * @return the request body builder - */ - public RequestBodyService getRequestBodyBuilder() { - return requestBodyService; - } - /** * Gets api parameters. * @@ -688,17 +696,57 @@ private void applyValidationsToSchema(Map annos, Schema s private boolean isRequestBodyParam(RequestMethod requestMethod, ParameterInfo parameterInfo, String openApiVersion) { MethodParameter methodParameter = parameterInfo.getMethodParameter(); DelegatingMethodParameter delegatingMethodParameter = (DelegatingMethodParameter) methodParameter; - Boolean isBodyAllowed = !RequestMethod.GET.equals(requestMethod) || OpenApiVersion.OPENAPI_3_1.getVersion().equals(openApiVersion); + boolean isBodyAllowed = !RequestMethod.GET.equals(requestMethod) || OpenApiVersion.OPENAPI_3_1.getVersion().equals(openApiVersion); + + return (isBodyAllowed && (parameterInfo.getParameterModel() == null + || parameterInfo.getParameterModel().getIn() == null) && !delegatingMethodParameter.isParameterObject()) && ((methodParameter.getParameterAnnotation(io.swagger.v3.oas.annotations.parameters.RequestBody.class) != null + || methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestBody.class) != null + || AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.parameters.RequestBody.class) != null) + || checkOperationRequestBody(methodParameter) + || checkFile(methodParameter)); + } - return (isBodyAllowed && (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getIn() == null) && !delegatingMethodParameter.isParameterObject()) && ((methodParameter.getParameterAnnotation(io.swagger.v3.oas.annotations.parameters.RequestBody.class) != null || methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestBody.class) != null || methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestPart.class) != null || AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.parameters.RequestBody.class) != null) || (!ClassUtils.isPrimitiveOrWrapper(methodParameter.getParameterType()) && (!ArrayUtils.isEmpty(methodParameter.getParameterAnnotations())))); + /** + * Check file boolean. + * + * @param methodParameter the method parameter + * @return the boolean + */ + private boolean checkFile(MethodParameter methodParameter) { + if (methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestPart.class) != null) + return true; + else if (methodParameter.getParameterAnnotation(org.springframework.web.bind.annotation.RequestParam.class) != null) { + return isFile(methodParameter.getParameterType()); + } + return false; } /** - * Is default flat param object boolean. + * Check operation request body boolean. * + * @param methodParameter the method parameter * @return the boolean */ - public boolean isDefaultFlatParamObject() { - return defaultFlatParamObject; + private boolean checkOperationRequestBody(MethodParameter methodParameter) { + if (AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.Operation.class) != null) { + io.swagger.v3.oas.annotations.Operation operation = AnnotatedElementUtils.findMergedAnnotation(Objects.requireNonNull(methodParameter.getMethod()), io.swagger.v3.oas.annotations.Operation.class); + io.swagger.v3.oas.annotations.parameters.RequestBody requestBody = operation.requestBody(); + if (StringUtils.isNotBlank(requestBody.description())) + return true; + else if (StringUtils.isNotBlank(requestBody.ref())) + return true; + else if (requestBody.required()) + return true; + else if (requestBody.useParameterTypeSchema()) + return true; + else if (requestBody.content().length > 0) + return true; + else + return requestBody.extensions().length > 0; + } + return false; } + + + } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractSwaggerUiConfigProperties.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractSwaggerUiConfigProperties.java index 45bf35270..9e90ee0e8 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractSwaggerUiConfigProperties.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/AbstractSwaggerUiConfigProperties.java @@ -30,7 +30,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.StringUtils; -import static org.springdoc.core.Constants.GROUP_NAME_NOT_NULL; +import static org.springdoc.core.Constants.GROUP_NAME_NOT_NULL_OR_EMPTY; /** * Please refer to the swagger @@ -722,7 +722,7 @@ public SwaggerUrl() { * @param displayName the display name */ public SwaggerUrl(String group, String url, String displayName) { - Objects.requireNonNull(group, GROUP_NAME_NOT_NULL); + Objects.requireNonNull(group, GROUP_NAME_NOT_NULL_OR_EMPTY); this.url = url; this.name = group; this.displayName = StringUtils.defaultIfEmpty(displayName, this.name); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/Constants.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/Constants.java index 9aa29127a..5b8cb56d4 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/Constants.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/Constants.java @@ -54,14 +54,14 @@ public final class Constants { public static final String API_DOCS_URL = "${springdoc.api-docs.path:#{T(org.springdoc.core.Constants).DEFAULT_API_DOCS_URL}}"; /** - * The constant SWAGGGER_CONFIG_FILE. + * The constant SWAGGER_CONFIG_FILE. */ - public static final String SWAGGGER_CONFIG_FILE = "swagger-config"; + public static final String SWAGGER_CONFIG_FILE = "swagger-config"; /** * The constant SWAGGER_CONFIG_URL. */ - public static final String SWAGGER_CONFIG_URL = API_DOCS_URL + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + public static final String SWAGGER_CONFIG_URL = API_DOCS_URL + DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; /** * The constant YAML. @@ -242,7 +242,7 @@ public final class Constants { /** * The constant GROUP_NAME_NOT_NULL. */ - public static final String GROUP_NAME_NOT_NULL = "Group name can not be null"; + public static final String GROUP_NAME_NOT_NULL_OR_EMPTY = "Group name can not be null or empty"; /** * The constant GET_METHOD. @@ -397,13 +397,28 @@ public final class Constants { /** * The constant SPRINGDOC_SORT_CONVERTER_ENABLED. */ - public static final String SPRINGDOC_SORT_CONVERTER_ENABLED = "springdoc.model-converters.sort-converter.enabled"; + public static final String SPRINGDOC_SORT_CONVERTER_ENABLED = "springdoc.sort-converter.enabled"; /** * The constant SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED. */ public static final String SPRINGDOC_NULLABLE_REQUEST_PARAMETER_ENABLED = "springdoc.nullable-request-parameter-enabled"; + /** + * The constant SPRINGDOC_SPECIFICATION_STRING_PROPERTIES. + */ + public static final String SPRINGDOC_SPECIFICATION_STRING_PROPERTIES = "springdoc.api-docs.specification-string-properties"; + + /** + * The constant SPRINGDOC_SPEC_PROPERTIES_PREFIX. + */ + public static final String SPRINGDOC_SPEC_PROPERTIES_PREFIX = "springdoc.spec-properties."; + + /** + * The constant SPRINGDOC_ENABLE_DEFAULT_API_DOCS. + */ + public static final String SPRINGDOC_ENABLE_DEFAULT_API_DOCS = "springdoc.enable-default-api-docs"; + /** * Instantiates a new Constants. */ diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/ControllerAdviceInfo.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/ControllerAdviceInfo.java index d7f6d9ce2..e4ca62564 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/ControllerAdviceInfo.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/ControllerAdviceInfo.java @@ -2,32 +2,39 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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 org.springdoc.core; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import io.swagger.v3.oas.models.responses.ApiResponse; +import org.springframework.util.CollectionUtils; + /** * The type Controller advice info. + * * @author bnasslahsen */ public class ControllerAdviceInfo { @@ -38,9 +45,9 @@ public class ControllerAdviceInfo { private final Object controllerAdvice; /** - * The Api response map. + * The Method advice infos. */ - private final Map apiResponseMap = new LinkedHashMap<>(); + private List methodAdviceInfos = new ArrayList<>(); /** * Instantiates a new Controller advice info. @@ -66,6 +73,19 @@ public Object getControllerAdvice() { * @return the api response map */ public Map getApiResponseMap() { + Map apiResponseMap = new LinkedHashMap<>(); + for (MethodAdviceInfo methodAdviceInfo : methodAdviceInfos) { + if (!CollectionUtils.isEmpty(methodAdviceInfo.getApiResponses())) + apiResponseMap.putAll(methodAdviceInfo.getApiResponses()); + } return apiResponseMap; } + + public List getMethodAdviceInfos() { + return methodAdviceInfos; + } + + public void addMethodAdviceInfos(MethodAdviceInfo methodAdviceInfo) { + this.methodAdviceInfos.add(methodAdviceInfo); + } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java index c9b415046..9d0feaa88 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/DelegatingMethodParameter.java @@ -48,6 +48,8 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestPart; /** * The type Delegating method parameter. @@ -119,19 +121,16 @@ public static MethodParameter[] customize(String[] pNames, MethodParameter[] par MethodParameter p = parameters[i]; Class paramClass = AdditionalModelsConverter.getParameterObjectReplacement(p.getParameterType()); - if (!MethodParameterPojoExtractor.isSimpleType(paramClass) && (p.hasParameterAnnotation(ParameterObject.class) || AnnotatedElementUtils.isAnnotated(paramClass, ParameterObject.class))) { + boolean hasFlatAnnotation = p.hasParameterAnnotation(ParameterObject.class) || AnnotatedElementUtils.isAnnotated(paramClass, ParameterObject.class); + boolean hasNotFlatAnnotation = Arrays.stream(p.getParameterAnnotations()) + .anyMatch(annotation -> Arrays.asList(RequestBody.class, RequestPart.class).contains(annotation.annotationType())); + if (!MethodParameterPojoExtractor.isSimpleType(paramClass) + && (hasFlatAnnotation || (defaultFlatParamObject && !hasNotFlatAnnotation && !AbstractRequestService.isRequestTypeToIgnore(paramClass)))) { MethodParameterPojoExtractor.extractFrom(paramClass).forEach(methodParameter -> { optionalDelegatingMethodParameterCustomizer.ifPresent(customizer -> customizer.customize(p, methodParameter)); explodedParameters.add(methodParameter); }); } - else if (defaultFlatParamObject && !MethodParameterPojoExtractor.isSimpleType(paramClass) && !AbstractRequestService.isRequestTypeToIgnore(paramClass)) { - MethodParameterPojoExtractor.extractFrom(paramClass).forEach(methodParameter -> { - optionalDelegatingMethodParameterCustomizer - .ifPresent(customizer -> customizer.customize(p, methodParameter)); - explodedParameters.add(methodParameter); - }); - } else { String name = pNames != null ? pNames[i] : p.getParameterName(); explodedParameters.add(new DelegatingMethodParameter(p, name, null, false, false)); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java index fac8c2e7f..a3396dce9 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericParameterService.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2023 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -46,6 +48,7 @@ import io.swagger.v3.oas.annotations.enums.ParameterStyle; import io.swagger.v3.oas.annotations.extensions.Extension; import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.examples.Example; import io.swagger.v3.oas.models.media.ArraySchema; @@ -78,7 +81,6 @@ /** * The type Generic parameter builder. - * * @author bnasslahsen, coutin */ @SuppressWarnings("rawtypes") @@ -136,8 +138,7 @@ public class GenericParameterService { private final Optional javadocProviderOptional; /** - * Instantiates a new Generic parameter service. - * + * Instantiates a new Generic parameter builder. * @param propertyResolverUtils the property resolver utils * @param optionalDelegatingMethodParameterCustomizer the optional delegating method parameter customizer * @param optionalWebConversionServiceProvider the optional web conversion service provider @@ -284,14 +285,14 @@ public Parameter buildParameterFromDoc(io.swagger.v3.oas.annotations.Parameter p parameter.setAllowReserved(parameterDoc.allowReserved()); if (parameterDoc.content().length > 0) { - Optional optionalContent = AnnotationsUtils.getContent(parameterDoc.content(), null, null, null, components, jsonView); + Optional optionalContent = AnnotationsUtils.getContent(parameterDoc.content(), null, null, null, components, jsonView, propertyResolverUtils.isOpenapi31()); optionalContent.ifPresent(parameter::setContent); } else setSchema(parameterDoc, components, jsonView, parameter); setExamples(parameterDoc, parameter); - setExtensions(parameterDoc, parameter); + setExtensions(parameterDoc, parameter, locale); setParameterStyle(parameter, parameterDoc); setParameterExplode(parameter, parameterDoc); @@ -312,7 +313,7 @@ private void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Com else { Schema schema = null; try { - schema = AnnotationsUtils.getSchema(parameterDoc.schema(), null, false, parameterDoc.schema().implementation(), components, jsonView).orElse(null); + schema = AnnotationsUtils.getSchema(parameterDoc.schema(), null, false, parameterDoc.schema().implementation(), components, jsonView, propertyResolverUtils.isOpenapi31()).orElse(null); // Cast default value if (schema != null && schema.getDefault() != null) { PrimitiveType primitiveType = PrimitiveType.fromTypeAndFormat(schema.getType(), schema.getFormat()); @@ -326,8 +327,8 @@ private void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Com catch (Exception e) { LOGGER.warn(Constants.GRACEFUL_EXCEPTION_OCCURRED, e); } - if (schema == null) { - schema = AnnotationsUtils.getSchema(parameterDoc.schema(), parameterDoc.array(), true, parameterDoc.array().schema().implementation(), components, jsonView).orElse(null); + if (schema == null && parameterDoc.array() != null) { + schema = AnnotationsUtils.getSchema(parameterDoc.schema(), parameterDoc.array(), true, parameterDoc.array().schema().implementation(), components, jsonView, propertyResolverUtils.isOpenapi31()).orElse(null); // default value not set by swagger-core for array ! if (schema != null) { Object defaultValue = SpringDocAnnotationsUtils.resolveDefaultValue(parameterDoc.array().arraySchema().defaultValue(), objectMapperProvider.jsonMapper()); @@ -354,15 +355,15 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque if (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getSchema() == null) { Type type = ReturnTypeParser.getType(methodParameter); - if (type instanceof Class && optionalWebConversionServiceProvider.isPresent()) { + if (type instanceof Class && !((Class) type).isEnum() && optionalWebConversionServiceProvider.isPresent()) { WebConversionServiceProvider webConversionServiceProvider = optionalWebConversionServiceProvider.get(); if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type) && methodParameter.getParameterType().getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class) == null) { Class springConvertedType = webConversionServiceProvider.getSpringConvertedType(methodParameter.getParameterType()); - if (!(String.class.equals(springConvertedType) && ((Class) type).isEnum()) && requestBodyInfo==null) + if (!(String.class.equals(springConvertedType) && ((Class) type).isEnum()) && requestBodyInfo == null) type = springConvertedType; } } - schemaN = SpringDocAnnotationsUtils.extractSchema(components, type, jsonView, methodParameter.getParameterAnnotations()); + schemaN = SpringDocAnnotationsUtils.extractSchema(components, type, jsonView, methodParameter.getParameterAnnotations(), propertyResolverUtils.getSpecVersion()); } else schemaN = parameterInfo.getParameterModel().getSchema(); @@ -451,12 +452,19 @@ private void setExamples(io.swagger.v3.oas.annotations.Parameter parameterDoc, P * Sets extensions. * * @param parameterDoc the parameter doc - * @param parameter the parameter + * @param parameter the parameter + * @param locale the locale */ - private void setExtensions(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) { + private void setExtensions(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter, Locale locale) { if (parameterDoc.extensions().length > 0) { - Map extensionMap = AnnotationsUtils.getExtensions(parameterDoc.extensions()); - extensionMap.forEach(parameter::addExtension); + Map extensionMap = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), parameterDoc.extensions()); + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensionMap); + extensionsResolved.forEach(parameter::addExtension); + } + else { + extensionMap.forEach(parameter::addExtension); + } } } @@ -573,7 +581,6 @@ public Optional getOptionalWebConversionServicePro /** * Resolve the given annotation-specified value, * potentially containing placeholders and expressions. - * * @param value the value * @return the object */ @@ -619,7 +626,9 @@ public String description() { @Override public boolean required() { - return schema.required(); + return schema.requiredMode().equals(RequiredMode.AUTO) ? + schema.required() : + schema.requiredMode().equals(RequiredMode.REQUIRED); } @Override @@ -742,7 +751,15 @@ String getParamJavadoc(JavadocProvider javadocProvider, MethodParameter methodPa if (StringUtils.isNotBlank(fieldJavadoc)) { paramJavadocDescription = fieldJavadoc; } - return paramJavadocDescription; } + + /** + * Is openapi 31 boolean. + * + * @return the boolean + */ + public boolean isOpenapi31() { + return propertyResolverUtils.isOpenapi31(); + } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java index d9f68856d..7a99f79be 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/GenericResponseService.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2023 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 + * * * * * Copyright 2019-2023 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. * * * * - * * * * 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. * * * * * * @@ -38,6 +40,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -56,7 +61,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springdoc.core.providers.JavadocProvider; -import org.springdoc.core.providers.ObjectMapperProvider; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; @@ -84,6 +88,7 @@ * @author bnasslahsen */ public class GenericResponseService { + /** * This extension name is used to temporary store * the exception classes. @@ -115,15 +120,20 @@ public class GenericResponseService { */ private final PropertyResolverUtils propertyResolverUtils; + /** + * The Controller advice infos. + */ + private final List controllerAdviceInfos = new CopyOnWriteArrayList<>(); + /** * The Controller infos. */ - private final List localExceptionHandlers = new ArrayList<>(); + private final List localExceptionHandlers = new CopyOnWriteArrayList<>(); /** - * The Controller advice infos. + * The Reentrant lock. */ - private final List controllerAdviceInfos = new ArrayList<>(); + private final Lock reentrantLock = new ReentrantLock(); /** * The constant LOGGER. @@ -133,10 +143,10 @@ public class GenericResponseService { /** * Instantiates a new Generic response builder. * - * @param operationService the operation builder - * @param returnTypeParsers the return type parsers + * @param operationService the operation builder + * @param returnTypeParsers the return type parsers * @param springDocConfigProperties the spring doc config properties - * @param propertyResolverUtils the property resolver utils + * @param propertyResolverUtils the property resolver utils */ public GenericResponseService(OperationService operationService, List returnTypeParsers, SpringDocConfigProperties springDocConfigProperties, @@ -151,20 +161,21 @@ public GenericResponseService(OperationService operationService, List optionalContent = getContent(contentdoc, new String[0], - methodAttributes.getMethodProduces(), null, components, methodAttributes.getJsonViewAnnotation()); + methodAttributes.getMethodProduces(), null, components, methodAttributes.getJsonViewAnnotation(), openapi31); if (apiResponsesOp.containsKey(apiResponseAnnotations.responseCode())) { // Merge with the existing content Content existingContent = apiResponsesOp.get(apiResponseAnnotations.responseCode()).getContent(); @@ -192,7 +203,7 @@ public static void buildContentFromDoc(Components components, ApiResponses apiRe /** * Sets description. * - * @param httpCode the http code + * @param httpCode the http code * @param apiResponse the api response */ public static void setDescription(String httpCode, ApiResponse apiResponse) { @@ -217,15 +228,15 @@ public static void setResponseEntityExceptionHandlerClass(Class responseEntit /** * Build api responses. * - * @param components the components - * @param handlerMethod the handler method - * @param operation the operation + * @param components the components + * @param handlerMethod the handler method + * @param operation the operation * @param methodAttributes the method attributes * @return the api responses */ public ApiResponses build(Components components, HandlerMethod handlerMethod, Operation operation, MethodAttributes methodAttributes) { - Map genericMapResponse = getGenericMapResponse(handlerMethod.getBeanType()); + Map genericMapResponse = getGenericMapResponse(handlerMethod); if (springDocConfigProperties.isOverrideWithGenericResponse()) { genericMapResponse = filterAndEnrichGenericMapResponseByDeclarations(handlerMethod, genericMapResponse); } @@ -236,7 +247,7 @@ public ApiResponses build(Components components, HandlerMethod handlerMethod, Op apiResponsesFromDoc.forEach(apiResponses::addApiResponse); // for each one build ApiResponse and add it to existing responses // Fill api Responses - computeResponseFromDoc(components, handlerMethod.getReturnType(), apiResponses, methodAttributes); + computeResponseFromDoc(components, handlerMethod.getReturnType(), apiResponses, methodAttributes, springDocConfigProperties.isOpenapi31()); buildApiResponses(components, handlerMethod.getReturnType(), apiResponses, methodAttributes); return apiResponses; } @@ -245,7 +256,7 @@ public ApiResponses build(Components components, HandlerMethod handlerMethod, Op * Filters the generic API responses by the declared exceptions. * If Javadoc comment found for the declaration than it overrides the default description. * - * @param handlerMethod the method which can have exception declarations + * @param handlerMethod the method which can have exception declarations * @param genericMapResponse the default generic API responses * @return the filtered and enriched responses */ @@ -274,9 +285,9 @@ private Map filterAndEnrichGenericMapResponseByDeclarations /** * Build generic response. * - * @param components the components + * @param components the components * @param findControllerAdvice the find controller advice - * @param locale the locale + * @param locale the locale */ public void buildGenericResponse(Components components, Map findControllerAdvice, Locale locale) { // ControllerAdvice @@ -299,8 +310,13 @@ public void buildGenericResponse(Components components, Map find String[] methodProduces = { springDocConfigProperties.getDefaultProducesMediaType() }; if (reqMappingMethod != null) methodProduces = reqMappingMethod.produces(); - Map controllerAdviceInfoApiResponseMap = controllerAdviceInfo.getApiResponseMap(); MethodParameter methodParameter = new MethodParameter(method, -1); + MethodAdviceInfo methodAdviceInfo = new MethodAdviceInfo(method); + controllerAdviceInfo.addMethodAdviceInfos(methodAdviceInfo); + // get exceptions lists + Set> exceptions = getExceptionsFromExceptionHandler(methodParameter); + methodAdviceInfo.setExceptions(exceptions); + Map controllerAdviceInfoApiResponseMap = controllerAdviceInfo.getApiResponseMap(); ApiResponses apiResponsesOp = new ApiResponses(); MethodAttributes methodAttributes = new MethodAttributes(methodProduces, springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), controllerAdviceInfoApiResponseMap, locale); @@ -311,18 +327,16 @@ public void buildGenericResponse(Components components, Map find JavadocProvider javadocProvider = operationService.getJavadocProvider(); methodAttributes.setJavadocReturn(javadocProvider.getMethodJavadocReturn(methodParameter.getMethod())); } - Map apiResponses = computeResponseFromDoc(components, methodParameter, apiResponsesOp, methodAttributes); + computeResponseFromDoc(components, methodParameter, apiResponsesOp, methodAttributes, springDocConfigProperties.isOpenapi31()); buildGenericApiResponses(components, methodParameter, apiResponsesOp, methodAttributes); - apiResponses.forEach(controllerAdviceInfoApiResponseMap::put); + methodAdviceInfo.setApiResponses(apiResponsesOp); } } - synchronized (this) { - if (AnnotatedElementUtils.hasAnnotation(objClz, ControllerAdvice.class)) { - controllerAdviceInfos.add(controllerAdviceInfo); - } - else { - localExceptionHandlers.add(controllerAdviceInfo); - } + if (AnnotatedElementUtils.hasAnnotation(objClz, ControllerAdvice.class)) { + controllerAdviceInfos.add(controllerAdviceInfo); + } + else { + localExceptionHandlers.add(controllerAdviceInfo); } } } @@ -342,14 +356,15 @@ private boolean isResponseEntityExceptionHandlerMethod(Method m) { /** * Compute response from doc map. * - * @param components the components - * @param methodParameter the method parameter - * @param apiResponsesOp the api responses op + * @param components the components + * @param methodParameter the method parameter + * @param apiResponsesOp the api responses op * @param methodAttributes the method attributes + * @param openapi31 the openapi 31 * @return the map */ private Map computeResponseFromDoc(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp, - MethodAttributes methodAttributes) { + MethodAttributes methodAttributes, boolean openapi31) { // Parsing documentation, if present Set responsesArray = getApiResponses(Objects.requireNonNull(methodParameter.getMethod())); if (!responsesArray.isEmpty()) { @@ -363,11 +378,18 @@ private Map computeResponseFromDoc(Components components, M continue; } apiResponse.setDescription(propertyResolverUtils.resolve(apiResponseAnnotations.description(), methodAttributes.getLocale())); - buildContentFromDoc(components, apiResponsesOp, methodAttributes, apiResponseAnnotations, apiResponse); - Map extensions = AnnotationsUtils.getExtensions(apiResponseAnnotations.extensions()); - if (!CollectionUtils.isEmpty(extensions)) - apiResponse.extensions(extensions); - AnnotationsUtils.getHeaders(apiResponseAnnotations.headers(), methodAttributes.getJsonViewAnnotation()) + buildContentFromDoc(components, apiResponsesOp, methodAttributes, apiResponseAnnotations, apiResponse, openapi31); + Map extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), apiResponseAnnotations.extensions()); + if (!CollectionUtils.isEmpty(extensions)){ + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(methodAttributes.getLocale(), extensions); + extensionsResolved.forEach(apiResponse::addExtension); + } + else { + apiResponse.extensions(extensions); + } + } + AnnotationsUtils.getHeaders(apiResponseAnnotations.headers(), methodAttributes.getJsonViewAnnotation(), openapi31) .ifPresent(apiResponse::headers); apiResponsesOp.addApiResponse(httpCode, apiResponse); } @@ -378,9 +400,9 @@ private Map computeResponseFromDoc(Components components, M /** * Build generic api responses. * - * @param components the components - * @param methodParameter the method parameter - * @param apiResponsesOp the api responses op + * @param components the components + * @param methodParameter the method parameter + * @param apiResponsesOp the api responses op * @param methodAttributes the method attributes */ private void buildGenericApiResponses(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp, @@ -408,9 +430,9 @@ private void buildGenericApiResponses(Components components, MethodParameter met /** * Build api responses. * - * @param components the components - * @param methodParameter the method parameter - * @param apiResponsesOp the api responses op + * @param components the components + * @param methodParameter the method parameter + * @param apiResponsesOp the api responses op * @param methodAttributes the method attributes */ private void buildApiResponses(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp, @@ -426,7 +448,6 @@ private void buildApiResponses(Components components, MethodParameter methodPara buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, apiResponse, false); } } - if (AnnotatedElementUtils.hasAnnotation(methodParameter.getMethod(), ResponseStatus.class)) { // Handles the case with @ResponseStatus, if the specified response is not already handled explicitly String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), false); @@ -434,7 +455,6 @@ private void buildApiResponses(Components components, MethodParameter methodPara buildApiResponses(components, methodParameter, apiResponsesOp, methodAttributes, httpCode, new ApiResponse(), false); } } - } else { String httpCode = evaluateResponseStatus(methodParameter.getMethod(), Objects.requireNonNull(methodParameter.getMethod()).getClass(), false); @@ -477,10 +497,10 @@ public Set getApiResponses( /** * Build content content. * - * @param components the components + * @param components the components * @param methodParameter the method parameter - * @param methodProduces the method produces - * @param jsonView the json view + * @param methodProduces the method produces + * @param jsonView the json view * @return the content */ private Content buildContent(Components components, MethodParameter methodParameter, String[] methodProduces, JsonView jsonView) { @@ -491,11 +511,11 @@ private Content buildContent(Components components, MethodParameter methodParame /** * Build content content. * - * @param components the components - * @param annotations the annotations + * @param components the components + * @param annotations the annotations * @param methodProduces the method produces - * @param jsonView the json view - * @param returnType the return type + * @param jsonView the json view + * @param returnType the return type * @return the content */ public Content buildContent(Components components, Annotation[] annotations, String[] methodProduces, JsonView jsonView, Type returnType) { @@ -537,15 +557,15 @@ private Type getReturnType(MethodParameter methodParameter) { /** * Calculate schema schema. * - * @param components the components - * @param returnType the return type - * @param jsonView the json view + * @param components the components + * @param returnType the return type + * @param jsonView the json view * @param annotations the annotations * @return the schema */ private Schema calculateSchema(Components components, Type returnType, JsonView jsonView, Annotation[] annotations) { if (!isVoid(returnType) && !SpringDocAnnotationsUtils.isAnnotationToIgnore(returnType)) - return extractSchema(components, returnType, jsonView, annotations); + return extractSchema(components, returnType, jsonView, annotations, propertyResolverUtils.getSpecVersion()); return null; } @@ -553,8 +573,8 @@ private Schema calculateSchema(Components components, Type returnType, JsonVi * Sets content. * * @param methodProduces the method produces - * @param content the content - * @param mediaType the media type + * @param content the content + * @param mediaType the media type */ private void setContent(String[] methodProduces, Content content, io.swagger.v3.oas.models.media.MediaType mediaType) { @@ -564,13 +584,13 @@ private void setContent(String[] methodProduces, Content content, /** * Build api responses. * - * @param components the components - * @param methodParameter the method parameter - * @param apiResponsesOp the api responses op + * @param components the components + * @param methodParameter the method parameter + * @param apiResponsesOp the api responses op * @param methodAttributes the method attributes - * @param httpCode the http code - * @param apiResponse the api response - * @param isGeneric the is generic + * @param httpCode the http code + * @param apiResponse the api response + * @param isGeneric the is generic */ private void buildApiResponses(Components components, MethodParameter methodParameter, ApiResponses apiResponsesOp, MethodAttributes methodAttributes, String httpCode, ApiResponse apiResponse, boolean isGeneric) { @@ -605,18 +625,7 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent())) && methodParameter.getExecutable().isAnnotationPresent(ExceptionHandler.class)) { // ExceptionHandler's exception class resolution is non-trivial // more info on its javadoc - ExceptionHandler exceptionHandler = methodParameter.getExecutable().getAnnotation(ExceptionHandler.class); - Set> exceptions = new HashSet<>(); - if (exceptionHandler.value().length == 0) { - for (Parameter parameter : methodParameter.getExecutable().getParameters()) { - if (Throwable.class.isAssignableFrom(parameter.getType())) { - exceptions.add(parameter.getType()); - } - } - } - else { - exceptions.addAll(asList(exceptionHandler.value())); - } + Set> exceptions = getExceptionsFromExceptionHandler(methodParameter); apiResponse.addExtension(EXTENSION_EXCEPTION_CLASSES, exceptions); } apiResponsesOp.addApiResponse(httpCode, apiResponse); @@ -625,8 +634,8 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent())) /** * Evaluate response status string. * - * @param method the method - * @param beanType the bean type + * @param method the method + * @param beanType the bean type * @param isGeneric the is generic * @return the string */ @@ -660,54 +669,81 @@ else if (returnType instanceof ParameterizedType) { return result; } + /** * Gets generic map response. * - * @param beanType the bean type + * @param handlerMethod the handler method * @return the generic map response */ - private synchronized Map getGenericMapResponse(Class beanType) { - List controllerAdviceInfosInThisBean = localExceptionHandlers.stream() - .filter(controllerInfo -> { - Class objClz = controllerInfo.getControllerAdvice().getClass(); - if (org.springframework.aop.support.AopUtils.isAopProxy(controllerInfo.getControllerAdvice())) - objClz = org.springframework.aop.support.AopUtils.getTargetClass(controllerInfo.getControllerAdvice()); - return beanType.equals(objClz); - }) - .collect(Collectors.toList()); - - Map genericApiResponseMap = controllerAdviceInfosInThisBean.stream() - .map(ControllerAdviceInfo::getApiResponseMap) - .collect(LinkedHashMap::new, Map::putAll, Map::putAll); - - List controllerAdviceInfosNotInThisBean = controllerAdviceInfos.stream() - .filter(controllerAdviceInfo -> - new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType)) - .collect(Collectors.toList()); - - for (ControllerAdviceInfo controllerAdviceInfo : controllerAdviceInfosNotInThisBean) { - controllerAdviceInfo.getApiResponseMap().forEach((key, apiResponse) -> { - if (!genericApiResponseMap.containsKey(key)) - genericApiResponseMap.put(key, apiResponse); - }); - } - - LinkedHashMap genericApiResponsesClone; + private Map getGenericMapResponse(HandlerMethod handlerMethod) { + reentrantLock.lock(); try { - ObjectMapper objectMapper = ObjectMapperProvider.createJson(springDocConfigProperties); - genericApiResponsesClone = objectMapper.readValue(objectMapper.writeValueAsString(genericApiResponseMap), ApiResponses.class); - return genericApiResponsesClone; + Class beanType = handlerMethod.getBeanType(); + List controllerAdviceInfosInThisBean = localExceptionHandlers.stream() + .filter(controllerInfo -> { + Class objClz = controllerInfo.getControllerAdvice().getClass(); + if (org.springframework.aop.support.AopUtils.isAopProxy(controllerInfo.getControllerAdvice())) + objClz = org.springframework.aop.support.AopUtils.getTargetClass(controllerInfo.getControllerAdvice()); + return beanType.equals(objClz); + }) + .collect(Collectors.toList()); + + Map genericApiResponseMap = controllerAdviceInfosInThisBean.stream() + .map(ControllerAdviceInfo::getApiResponseMap) + .collect(LinkedHashMap::new, Map::putAll, Map::putAll); + + List controllerAdviceInfosNotInThisBean = controllerAdviceInfos.stream() + .filter(controllerAdviceInfo -> + new ControllerAdviceBean(controllerAdviceInfo.getControllerAdvice()).isApplicableToBeanType(beanType)) + .collect(Collectors.toList()); + + Class[] methodExceptions = handlerMethod.getMethod().getExceptionTypes(); + + for (ControllerAdviceInfo controllerAdviceInfo : controllerAdviceInfosNotInThisBean) { + List methodAdviceInfos = controllerAdviceInfo.getMethodAdviceInfos(); + for (MethodAdviceInfo methodAdviceInfo : methodAdviceInfos) { + Set> exceptions = methodAdviceInfo.getExceptions(); + boolean addToGenericMap = false; + for (Class exception : exceptions) { + if (isGlobalException(exception) || + Arrays.stream(methodExceptions).anyMatch(methodException -> + methodException.isAssignableFrom(exception) || + exception.isAssignableFrom(methodException))) { + addToGenericMap = true; + break; + } + } + + if (addToGenericMap || exceptions.isEmpty()) { + methodAdviceInfo.getApiResponses().forEach((key, apiResponse) -> { + if (!genericApiResponseMap.containsKey(key)) + genericApiResponseMap.put(key, apiResponse); + }); + } + } + } + + LinkedHashMap genericApiResponsesClone; + try { + ObjectMapper objectMapper = new ObjectMapper(); + genericApiResponsesClone = objectMapper.readValue(objectMapper.writeValueAsString(genericApiResponseMap), ApiResponses.class); + return genericApiResponsesClone; + } + catch (JsonProcessingException e) { + LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); + return genericApiResponseMap; + } } - catch (JsonProcessingException e) { - LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); - return genericApiResponseMap; + finally { + reentrantLock.unlock(); } } /** * Is valid http code boolean. * - * @param httpCode the http code + * @param httpCode the http code * @param methodParameter the method parameter * @return the boolean */ @@ -726,7 +762,7 @@ private boolean isValidHttpCode(String httpCode, MethodParameter methodParameter if (isHttpCodePresent(httpCode, responseSet)) result = true; } - else if (httpCode.equals(evaluateResponseStatus(method, method.getClass(), false))) + if (httpCode.equals(evaluateResponseStatus(method, method.getClass(), false))) result = true; } } @@ -736,11 +772,48 @@ else if (httpCode.equals(evaluateResponseStatus(method, method.getClass(), false /** * Is http code present boolean. * - * @param httpCode the http code + * @param httpCode the http code * @param responseSet the response set * @return the boolean */ private boolean isHttpCodePresent(String httpCode, Set responseSet) { return !responseSet.isEmpty() && responseSet.stream().anyMatch(apiResponseAnnotations -> httpCode.equals(apiResponseAnnotations.responseCode())); } + + /** + * Gets exceptions from exception handler. + * + * @param methodParameter the method parameter + * @return the exceptions from exception handler + */ + private Set> getExceptionsFromExceptionHandler(MethodParameter methodParameter) { + ExceptionHandler exceptionHandler = methodParameter.getMethodAnnotation(ExceptionHandler.class); + Set> exceptions = new HashSet<>(); + if (exceptionHandler != null) { + if (exceptionHandler.value().length == 0) { + for (Parameter parameter : methodParameter.getMethod().getParameters()) { + if (Throwable.class.isAssignableFrom(parameter.getType())) { + exceptions.add(parameter.getType()); + } + } + } + else { + exceptions.addAll(asList(exceptionHandler.value())); + } + } + return exceptions; + } + + + /** + * Is unchecked exception boolean. + * + * @param exceptionClass the exception class + * @return the boolean + */ + private boolean isGlobalException(Class exceptionClass) { + return RuntimeException.class.isAssignableFrom(exceptionClass) + || exceptionClass.isAssignableFrom(Exception.class) + || Error.class.isAssignableFrom(exceptionClass); + } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/GroupedOpenApi.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/GroupedOpenApi.java index 4b908612b..38938ebf7 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/GroupedOpenApi.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/GroupedOpenApi.java @@ -34,9 +34,10 @@ import org.springdoc.core.customizers.RouterOperationCustomizer; import org.springdoc.core.filters.OpenApiMethodFilter; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import static org.springdoc.core.Constants.GROUP_NAME_NOT_NULL; +import static org.springdoc.core.Constants.GROUP_NAME_NOT_NULL_OR_EMPTY; /** * The type Grouped open api. @@ -115,7 +116,8 @@ public class GroupedOpenApi { * @param builder the builder */ private GroupedOpenApi(Builder builder) { - this.group = Objects.requireNonNull(builder.group, GROUP_NAME_NOT_NULL); + Assert.isTrue(StringUtils.isNotBlank(builder.group), GROUP_NAME_NOT_NULL_OR_EMPTY); + this.group =builder.group; this.pathsToMatch = builder.pathsToMatch; this.packagesToScan = builder.packagesToScan; this.producesToMatch = builder.producesToMatch; diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodAdviceInfo.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodAdviceInfo.java new file mode 100644 index 000000000..e4d9cc957 --- /dev/null +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodAdviceInfo.java @@ -0,0 +1,106 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 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 org.springdoc.core; + +import java.lang.reflect.Method; +import java.util.Set; + +import io.swagger.v3.oas.models.responses.ApiResponses; + +/** + * The type Method advice info. + * + * @author bnasslahsen + */ +public class MethodAdviceInfo { + + /** + * The Method. + */ + private final Method method; + + /** + * The Exceptions. + */ + private Set> exceptions; + + /** + * The Api responses. + */ + private ApiResponses apiResponses; + + /** + * Instantiates a new Method advice info. + * + * @param method the method + */ + public MethodAdviceInfo(Method method) { + this.method = method; + } + + /** + * Gets method. + * + * @return the method + */ + public Method getMethod() { + return method; + } + + /** + * Gets exceptions. + * + * @return the exceptions + */ + public Set> getExceptions() { + return exceptions; + } + + /** + * Sets exceptions. + * + * @param exceptions the exceptions + */ + public void setExceptions(Set> exceptions) { + this.exceptions = exceptions; + } + + /** + * Gets api responses. + * + * @return the api responses + */ + public ApiResponses getApiResponses() { + return apiResponses; + } + + /** + * Sets api responses. + * + * @param apiResponses the api responses + */ + public void setApiResponses(ApiResponses apiResponses) { + this.apiResponses = apiResponses; + } +} \ No newline at end of file diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java index d03406603..0b49dc138 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/MethodParameterPojoExtractor.java @@ -51,6 +51,7 @@ import io.swagger.v3.core.util.PrimitiveType; import io.swagger.v3.oas.annotations.Parameter; +import org.springdoc.core.converters.ConverterUtils; import org.springframework.core.MethodParameter; @@ -91,6 +92,9 @@ class MethodParameterPojoExtractor { SIMPLE_TYPE_PREDICATES.add(Class::isEnum); SIMPLE_TYPE_PREDICATES.add(Class::isArray); SIMPLE_TYPE_PREDICATES.add(MethodParameterPojoExtractor::isSwaggerPrimitiveType); + SIMPLE_TYPE_PREDICATES.add(aClass -> aClass.getName().startsWith("org.codehaus.groovy.reflection")); + SIMPLE_TYPE_PREDICATES.add(aClass -> aClass.getName().startsWith("groovy.lang")); + SIMPLE_TYPE_PREDICATES.add(aClass -> aClass.getName().startsWith("java.lang.ref")); } /** @@ -134,7 +138,7 @@ private static Stream extractFrom(Class clazz, String fieldN private static Stream fromGetterOfField(Class paramClass, Field field, String fieldNamePrefix) { Class type = extractType(paramClass, field); - if (Objects.isNull(type)) + if (Objects.isNull(type) || ConverterUtils.isJavaTypeToIgnore(type)) return Stream.empty(); if (isSimpleType(type)) diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java index 36322a707..69420e2bf 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java @@ -37,7 +37,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.core.jackson.TypeNameResolver; @@ -61,7 +60,6 @@ import org.springdoc.core.customizers.OpenApiBuilderCustomizer; import org.springdoc.core.customizers.ServerBaseUrlCustomizer; import org.springdoc.core.providers.JavadocProvider; -import org.springdoc.core.providers.ObjectMapperProvider; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; @@ -242,20 +240,18 @@ public OpenAPI build(Locale locale) { Optional apiDef = getOpenAPIDefinition(); OpenAPI calculatedOpenAPI = null; if (openAPI == null) { - calculatedOpenAPI = new OpenAPI(); + calculatedOpenAPI = new OpenAPI(springDocConfigProperties.getSpecVersion()); calculatedOpenAPI.setComponents(new Components()); calculatedOpenAPI.setPaths(new Paths()); } else { try { - ObjectMapper objectMapper = ObjectMapperProvider.createJson(springDocConfigProperties); + ObjectMapper objectMapper = new ObjectMapper(); calculatedOpenAPI = objectMapper.readValue(objectMapper.writeValueAsString(openAPI), OpenAPI.class); - objectMapper.setSerializationInclusion(Include.ALWAYS); - Map extensionsClone = objectMapper.readValue(objectMapper.writeValueAsString(openAPI.getExtensions()), Map.class); - calculatedOpenAPI.extensions(extensionsClone); } catch (JsonProcessingException e) { LOGGER.warn("Json Processing Exception occurred: {}", e.getMessage()); + calculatedOpenAPI = openAPI; } } @@ -504,11 +500,11 @@ private Optional getOpenAPIDefinition() { */ private void buildOpenAPIWithOpenAPIDefinition(OpenAPI openAPI, OpenAPIDefinition apiDef, Locale locale) { // info - AnnotationsUtils.getInfo(apiDef.info()).map(info -> propertyResolverUtils.resolveProperties(info, locale)).ifPresent(openAPI::setInfo); + AnnotationsUtils.getInfo(apiDef.info(), propertyResolverUtils.isOpenapi31()).map(info -> propertyResolverUtils.resolveProperties(info, locale)).ifPresent(openAPI::setInfo); // OpenApiDefinition security requirements securityParser.getSecurityRequirements(apiDef.security()).ifPresent(openAPI::setSecurity); // OpenApiDefinition external docs - AnnotationsUtils.getExternalDocumentation(apiDef.externalDocs()).ifPresent(openAPI::setExternalDocs); + AnnotationsUtils.getExternalDocumentation(apiDef.externalDocs(), propertyResolverUtils.isOpenapi31()).ifPresent(openAPI::setExternalDocs); // OpenApiDefinition tags AnnotationsUtils.getTags(apiDef.tags(), false).ifPresent(tags -> openAPI.setTags(new ArrayList<>(tags))); // OpenApiDefinition servers @@ -520,7 +516,7 @@ private void buildOpenAPIWithOpenAPIDefinition(OpenAPI openAPI, OpenAPIDefinitio ); // OpenApiDefinition extensions if (apiDef.extensions().length > 0) { - openAPI.setExtensions(AnnotationsUtils.getExtensions(apiDef.extensions())); + openAPI.setExtensions(AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), apiDef.extensions())); } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/OperationService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/OperationService.java index edf557714..556ca55f2 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/OperationService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/OperationService.java @@ -2,24 +2,27 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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 org.springdoc.core; import java.lang.reflect.Method; @@ -134,7 +137,7 @@ public OpenAPI parse(io.swagger.v3.oas.annotations.Operation apiOperation, buildTags(apiOperation, operation); if (operation.getExternalDocs() == null) // if not set in root annotation - AnnotationsUtils.getExternalDocumentation(apiOperation.externalDocs()) + AnnotationsUtils.getExternalDocumentation(apiOperation.externalDocs(), propertyResolverUtils.isOpenapi31()) .ifPresent(operation::setExternalDocs); // servers @@ -149,7 +152,8 @@ public OpenAPI parse(io.swagger.v3.oas.annotations.Operation apiOperation, } // RequestBody in Operation - requestBodyService.buildRequestBodyFromDoc(apiOperation.requestBody(), operation.getRequestBody(), methodAttributes, components).ifPresent(operation::setRequestBody); + requestBodyService.buildRequestBodyFromDoc(apiOperation.requestBody(), operation.getRequestBody(), methodAttributes, components, locale) + .ifPresent(operation::setRequestBody); // build response buildResponse(components, apiOperation, operation, methodAttributes); @@ -158,7 +162,7 @@ public OpenAPI parse(io.swagger.v3.oas.annotations.Operation apiOperation, securityParser.buildSecurityRequirement(apiOperation.security(), operation); // Extensions in Operation - buildExtensions(apiOperation, operation); + buildExtensions(apiOperation, operation,locale); return openAPI; } @@ -268,10 +272,16 @@ private void setPathItemOperation(PathItem pathItemObject, String method, Operat * @param apiOperation the api operation * @param operation the operation */ - private void buildExtensions(io.swagger.v3.oas.annotations.Operation apiOperation, Operation operation) { + private void buildExtensions(io.swagger.v3.oas.annotations.Operation apiOperation, Operation operation, Locale locale) { if (apiOperation.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(apiOperation.extensions()); - extensions.forEach(operation::addExtension); + Map extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), apiOperation.extensions()); + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions); + extensionsResolved.forEach(operation::addExtension); + } + else { + extensions.forEach(operation::addExtension); + } } } @@ -394,11 +404,11 @@ private Optional getApiResponses( continue; } setDescription(response, apiResponseObject, methodAttributes.getJavadocReturn()); - setExtensions(response, apiResponseObject); + setExtensions(response, apiResponseObject, methodAttributes.getLocale()); buildResponseContent(methodAttributes, components, classProduces, methodProduces, apiResponsesOp, response, apiResponseObject); - AnnotationsUtils.getHeaders(response.headers(), null).ifPresent(apiResponseObject::headers); + AnnotationsUtils.getHeaders(response.headers(), null, propertyResolverUtils.isOpenapi31()).ifPresent(apiResponseObject::headers); // Make schema as string if empty calculateHeader(apiResponseObject); if (isResponseObject(apiResponseObject)) { @@ -430,10 +440,10 @@ private void buildResponseContent(MethodAttributes methodAttributes, Components if (apiResponsesOp == null) SpringDocAnnotationsUtils.getContent(response.content(), classProduces == null ? new String[0] : classProduces, - methodProduces == null ? new String[0] : methodProduces, null, components, null) + methodProduces == null ? new String[0] : methodProduces, null, components, null, propertyResolverUtils.isOpenapi31()) .ifPresent(apiResponseObject::content); else - GenericResponseService.buildContentFromDoc(components, apiResponsesOp, methodAttributes, response, apiResponseObject); + GenericResponseService.buildContentFromDoc(components, apiResponsesOp, methodAttributes, response, apiResponseObject, propertyResolverUtils.isOpenapi31()); } /** @@ -454,7 +464,7 @@ private boolean isResponseObject(ApiResponse apiResponseObject) { * @param apiResponseObject the api response object */ private void setLinks(io.swagger.v3.oas.annotations.responses.ApiResponse response, ApiResponse apiResponseObject) { - Map links = AnnotationsUtils.getLinks(response.links()); + Map links = AnnotationsUtils.getLinks(response.links(), propertyResolverUtils.isOpenapi31()); if (links.size() > 0) { apiResponseObject.setLinks(links); } @@ -464,8 +474,8 @@ private void setLinks(io.swagger.v3.oas.annotations.responses.ApiResponse respon * Sets description. * * @param response the response + * @param response the javadocReturn * @param apiResponseObject the api response object - * @param javadocReturn the javadoc return */ private void setDescription(io.swagger.v3.oas.annotations.responses.ApiResponse response, ApiResponse apiResponseObject, String javadocReturn) { @@ -491,7 +501,7 @@ private void calculateHeader(ApiResponse apiResponseObject) { for (Map.Entry entry : headers.entrySet()) { Header header = entry.getValue(); if (header.getSchema() == null) { - Schema schema = AnnotationsUtils.resolveSchemaFromType(String.class, null, null); + Schema schema = AnnotationsUtils.resolveSchemaFromType(String.class, null, null, propertyResolverUtils.isOpenapi31()); header.setSchema(schema); entry.setValue(header); } @@ -520,14 +530,21 @@ private void setRef(ApiResponses apiResponsesObject, io.swagger.v3.oas.annotatio /** * Sets extensions. * - * @param response the response + * @param response the response * @param apiResponseObject the api response object + * @param locale the locale */ private void setExtensions(io.swagger.v3.oas.annotations.responses.ApiResponse response, - ApiResponse apiResponseObject) { + ApiResponse apiResponseObject, Locale locale) { if (response.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(response.extensions()); - extensions.forEach(apiResponseObject::addExtension); + Map extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), response.extensions()); + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions); + extensionsResolved.forEach(apiResponseObject::addExtension); + } + else { + extensions.forEach(apiResponseObject::addExtension); + } } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/PropertyResolverUtils.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/PropertyResolverUtils.java index a1b6db47b..65704fc52 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/PropertyResolverUtils.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/PropertyResolverUtils.java @@ -22,6 +22,7 @@ package org.springdoc.core; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -31,6 +32,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; @@ -155,6 +157,12 @@ public Info resolveProperties(Info info, Locale locale) { resolveProperty(contact::getEmail, contact::email, this, locale); resolveProperty(contact::getUrl, contact::url, this, locale); } + + if(isResolveExtensionsProperties()){ + Map extensionsResolved = resolveExtensions(locale, info.getExtensions()); + info.setExtensions(extensionsResolved); + } + return info; } @@ -221,4 +229,61 @@ public ConfigurableBeanFactory getFactory() { public SpringDocConfigProperties getSpringDocConfigProperties() { return springDocConfigProperties; } + + /** + * Gets spec version. + * + * @return the spec version + */ + public SpecVersion getSpecVersion() { + return springDocConfigProperties.getSpecVersion(); + } + + /** + * Is openapi 31 boolean. + * + * @return the boolean + */ + public boolean isOpenapi31() { + return springDocConfigProperties.isOpenapi31(); + } + + + /** + * Is resolve extensions properties boolean. + * + * @return the boolean + */ + public boolean isResolveExtensionsProperties() { + return springDocConfigProperties.getApiDocs().isResolveExtensionsProperties(); + } + /** + * Resolve extensions map. + * + * @param locale the locale + * @param extensions the extensions + * @return the map + */ + public Map resolveExtensions(Locale locale, Map extensions) { + if (!CollectionUtils.isEmpty(extensions)) { + Map extensionsResolved = new HashMap<>(); + extensions.forEach((key, value) -> { + String keyResolved = resolve(key, locale); + if (value instanceof HashMap) { + Map valueResolved = new HashMap<>(); + ((HashMap) value).forEach((key1, value1) -> { + String key1Resolved = resolve(key1.toString(), locale); + String value1Resolved = resolve(value1.toString(), locale); + valueResolved.put(key1Resolved, value1Resolved); + }); + extensionsResolved.put(keyResolved, valueResolved); + } + else + extensionsResolved.put(keyResolved, value); + }); + return extensionsResolved; + } + else + return extensions; + } } \ No newline at end of file diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java index 98a5e22a7..1c9b6beee 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/RequestBodyService.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -23,6 +25,7 @@ package org.springdoc.core; import java.util.Arrays; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -52,16 +55,24 @@ public class RequestBodyService { */ private final GenericParameterService parameterBuilder; + /** + * The Property resolver utils. + */ + private final PropertyResolverUtils propertyResolverUtils; + /** * Instantiates a new Request body builder. * * @param parameterBuilder the parameter builder + * @param propertyResolverUtils the property resolver utils */ - public RequestBodyService(GenericParameterService parameterBuilder) { + public RequestBodyService(GenericParameterService parameterBuilder, PropertyResolverUtils propertyResolverUtils) { super(); this.parameterBuilder = parameterBuilder; + this.propertyResolverUtils = propertyResolverUtils; } + /** * Build request body from doc optional. * @@ -70,11 +81,12 @@ public RequestBodyService(GenericParameterService parameterBuilder) { * @param methodAttributes the method attributes * @param components the components * @param jsonViewAnnotation the json view annotation + * @param locale the locale * @return the optional */ public Optional buildRequestBodyFromDoc( io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, - Components components, JsonView jsonViewAnnotation) { + Components components, JsonView jsonViewAnnotation, Locale locale) { String[] classConsumes = methodAttributes.getClassConsumes(); String[] methodConsumes = methodAttributes.getMethodConsumes(); @@ -89,7 +101,7 @@ public Optional buildRequestBodyFromDoc( } if (StringUtils.isNotBlank(requestBody.description())) { - requestBodyObject.setDescription(requestBody.description()); + requestBodyObject.setDescription(propertyResolverUtils.resolve(requestBody.description(), locale)); isEmpty = false; } @@ -98,7 +110,7 @@ public Optional buildRequestBodyFromDoc( isEmpty = false; } if (requestBody.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(requestBody.extensions()); + Map extensions = AnnotationsUtils.getExtensions(parameterBuilder.isOpenapi31(), requestBody.extensions()); extensions.forEach(requestBodyObject::addExtension); isEmpty = false; } @@ -129,7 +141,7 @@ public Optional buildRequestBodyFromDoc( private void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation, String[] classConsumes, String[] methodConsumes, RequestBody requestBodyObject) { Optional optionalContent = SpringDocAnnotationsUtils .getContent(requestBody.content(), getConsumes(classConsumes), - getConsumes(methodConsumes), null, components, jsonViewAnnotation); + getConsumes(methodConsumes), null, components, jsonViewAnnotation, parameterBuilder.isOpenapi31()); if (requestBodyOp == null) { if (optionalContent.isPresent()) { Content content = optionalContent.get(); @@ -185,8 +197,7 @@ private String[] getConsumes(String[] classConsumes) { */ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, MethodAttributes methodAttributes, Components components) { - return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes, - components, null); + return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes, components, null, null); } /** @@ -196,12 +207,13 @@ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotatio * @param methodAttributes the method attributes * @param components the components * @param jsonViewAnnotation the json view annotation + * @param locale the locale * @return the optional */ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, - MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation) { + MethodAttributes methodAttributes, Components components, JsonView jsonViewAnnotation, Locale locale) { return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes, - components, jsonViewAnnotation); + components, jsonViewAnnotation, locale); } /** @@ -215,9 +227,8 @@ public Optional buildRequestBodyFromDoc(io.swagger.v3.oas.annotatio */ public Optional buildRequestBodyFromDoc( io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes, - Components components) { - return this.buildRequestBodyFromDoc(requestBody, requestBodyOp, methodAttributes, - components, null); + Components components, Locale locale) { + return this.buildRequestBodyFromDoc(requestBody, requestBodyOp, methodAttributes, components, null, locale); } /** diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/ReturnTypeParser.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/ReturnTypeParser.java index 9d7316a85..3ae33c95e 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/ReturnTypeParser.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/ReturnTypeParser.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -25,6 +27,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.util.Arrays; import java.util.Objects; import org.springframework.core.MethodParameter; @@ -90,7 +93,8 @@ static void resolveType(ResolvableType[] resolvableTypes, Class contextClass) resolvableTypes[i] = resolvableType; } else if (resolvableTypes[i].hasGenerics()) { - resolveType(resolvableTypes[i].getGenerics(), contextClass); + if(!Arrays.equals(resolvableTypes[i].getGenerics(), resolvableTypes)) + resolveType(resolvableTypes[i].getGenerics(), contextClass); if (resolvableTypes[i].getRawClass() != null) resolvableTypes[i] = ResolvableType.forClassWithGenerics(Objects.requireNonNull(resolvableTypes[i].getRawClass()), resolvableTypes[i].getGenerics()); } @@ -199,4 +203,4 @@ default Type getReturnType(MethodParameter methodParameter) { return methodParameter.getParameterType(); } -} +} \ No newline at end of file diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SecurityService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SecurityService.java index ec9fa4b67..2e544980a 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SecurityService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SecurityService.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -218,7 +220,7 @@ public Optional> getSecurityRequirements( * @param locale the locale * @return the security scheme */ - public Optional getSecurityScheme( + Optional getSecurityScheme( io.swagger.v3.oas.annotations.security.SecurityScheme securityScheme, Locale locale) { if (securityScheme == null) return Optional.empty(); @@ -254,11 +256,17 @@ public Optional getSecurityScheme( if (StringUtils.isNotBlank(securityScheme.paramName())) securitySchemeObject.setName(securityScheme.paramName()); - if (securityScheme.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(securityScheme.extensions()); - extensions.forEach(securitySchemeObject::addExtension); - } - + if (securityScheme.extensions().length > 0) { + Map extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), securityScheme.extensions()); + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions); + extensionsResolved.forEach(securitySchemeObject::addExtension); + } + else { + extensions.forEach(securitySchemeObject::addExtension); + } + } + getOAuthFlows(securityScheme.flows(), locale).ifPresent(securitySchemeObject::setFlows); SecuritySchemePair result = new SecuritySchemePair(key, securitySchemeObject); @@ -292,8 +300,14 @@ private Optional getOAuthFlows(io.swagger.v3.oas.annotations.securit OAuthFlows oAuthFlowsObject = new OAuthFlows(); if (oAuthFlows.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(oAuthFlows.extensions()); - extensions.forEach(oAuthFlowsObject::addExtension); + Map extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), oAuthFlows.extensions()); + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions); + extensionsResolved.forEach(oAuthFlowsObject::addExtension); + } + else { + extensions.forEach(oAuthFlowsObject::addExtension); + } } getOAuthFlow(oAuthFlows.authorizationCode(), locale).ifPresent(oAuthFlowsObject::setAuthorizationCode); getOAuthFlow(oAuthFlows.clientCredentials(), locale).ifPresent(oAuthFlowsObject::setClientCredentials); @@ -324,8 +338,14 @@ private Optional getOAuthFlow(io.swagger.v3.oas.annotations.security. oAuthFlowObject.setTokenUrl(propertyResolverUtils.resolve(oAuthFlow.tokenUrl(), locale)); if (oAuthFlow.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(oAuthFlow.extensions()); - extensions.forEach(oAuthFlowObject::addExtension); + Map extensions = AnnotationsUtils.getExtensions(propertyResolverUtils.isOpenapi31(), oAuthFlow.extensions()); + if (propertyResolverUtils.isResolveExtensionsProperties()) { + Map extensionsResolved = propertyResolverUtils.resolveExtensions(locale, extensions); + extensionsResolved.forEach(oAuthFlowObject::addExtension); + } + else { + extensions.forEach(oAuthFlowObject::addExtension); + } } getScopes(oAuthFlow.scopes()).ifPresent(oAuthFlowObject::setScopes); return Optional.of(oAuthFlowObject); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpecPropertiesCondition.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpecPropertiesCondition.java new file mode 100644 index 000000000..28baab84a --- /dev/null +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpecPropertiesCondition.java @@ -0,0 +1,59 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 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 org.springdoc.core; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.type.AnnotatedTypeMetadata; + +import static org.springdoc.core.Constants.SPRINGDOC_SPEC_PROPERTIES_PREFIX; + +/** + * The type Spec properties condition. + * + * @author bnasslahsen + */ +public class SpecPropertiesCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + MutablePropertySources propertySources = ((ConfigurableEnvironment) context.getEnvironment()) + .getPropertySources(); + for (PropertySource propertySource : propertySources) { + if (propertySource instanceof EnumerablePropertySource) { + String[] propertyNames = ((EnumerablePropertySource) propertySource).getPropertyNames(); + for (String name : propertyNames) { + if (name.startsWith(SPRINGDOC_SPEC_PROPERTIES_PREFIX)) { + return true; + } + } + } + } + return false; + } +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocAnnotationsUtils.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocAnnotationsUtils.java index 13326728d..1b9c6bbda 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocAnnotationsUtils.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocAnnotationsUtils.java @@ -2,26 +2,27 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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 org.springdoc.core; - import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @@ -44,6 +45,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.SchemaProperty; import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Content; @@ -87,14 +89,15 @@ public class SpringDocAnnotationsUtils extends AnnotationsUtils { * Resolve schema from type schema. * * @param schemaImplementation the schema implementation - * @param components the components - * @param jsonView the json view - * @param annotations the annotations + * @param components the components + * @param jsonView the json view + * @param annotations the annotations + * @param specVersion the spec version * @return the schema */ public static Schema resolveSchemaFromType(Class schemaImplementation, Components components, - JsonView jsonView, Annotation[] annotations) { - Schema schemaObject = extractSchema(components, schemaImplementation, jsonView, annotations); + JsonView jsonView, Annotation[] annotations, SpecVersion specVersion) { + Schema schemaObject = extractSchema(components, schemaImplementation, jsonView, annotations, specVersion); if (schemaObject != null && StringUtils.isBlank(schemaObject.get$ref()) && StringUtils.isBlank(schemaObject.getType()) && !(schemaObject instanceof ComposedSchema)) { // default to string @@ -106,17 +109,19 @@ public static Schema resolveSchemaFromType(Class schemaImplementation, Compon /** * Extract schema schema. * - * @param components the components - * @param returnType the return type - * @param jsonView the json view + * @param components the components + * @param returnType the return type + * @param jsonView the json view * @param annotations the annotations + * @param specVersion the spec version * @return the schema */ - public static Schema extractSchema(Components components, Type returnType, JsonView jsonView, Annotation[] annotations) { + public static Schema extractSchema(Components components, Type returnType, JsonView jsonView, Annotation[] annotations, SpecVersion specVersion) { Schema schemaN = null; ResolvedSchema resolvedSchema; + boolean openapi31 = SpecVersion.V31 == specVersion; try { - resolvedSchema = ModelConverters.getInstance() + resolvedSchema = ModelConverters.getInstance(openapi31) .resolveAsResolvedSchema( new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonView).ctxAnnotations(annotations)); } @@ -158,16 +163,17 @@ public static Schema extractSchema(Components components, Type returnType, JsonV * Gets content. * * @param annotationContents the annotation contents - * @param classTypes the class types - * @param methodTypes the method types - * @param schema the schema - * @param components the components + * @param classTypes the class types + * @param methodTypes the method types + * @param schema the schema + * @param components the components * @param jsonViewAnnotation the json view annotation + * @param openapi31 the openapi 31 * @return the content */ public static Optional getContent(io.swagger.v3.oas.annotations.media.Content[] annotationContents, String[] classTypes, String[] methodTypes, Schema schema, Components components, - JsonView jsonViewAnnotation) { + JsonView jsonViewAnnotation, boolean openapi31) { if (ArrayUtils.isEmpty(annotationContents)) { return Optional.empty(); } @@ -175,17 +181,19 @@ public static Optional getContent(io.swagger.v3.oas.annotations.media.C Content content = new Content(); for (io.swagger.v3.oas.annotations.media.Content annotationContent : annotationContents) { - MediaType mediaType = getMediaType(schema, components, jsonViewAnnotation, annotationContent); + MediaType mediaType = getMediaType(schema, components, jsonViewAnnotation, annotationContent, openapi31); ExampleObject[] examples = annotationContent.examples(); setExamples(mediaType, examples); - addExtension(annotationContent, mediaType); + addExtension(annotationContent, mediaType, openapi31); io.swagger.v3.oas.annotations.media.Encoding[] encodings = annotationContent.encoding(); - addEncodingToMediaType(jsonViewAnnotation, mediaType, encodings); + addEncodingToMediaType(jsonViewAnnotation, mediaType, encodings, openapi31); if (StringUtils.isNotBlank(annotationContent.mediaType())) { content.addMediaType(annotationContent.mediaType(), mediaType); } - else if (mediaType.getSchema() != null || mediaType.getEncoding() != null || mediaType.getExample() != null || mediaType.getExamples() != null || mediaType.getExtensions() != null) - applyTypes(classTypes, methodTypes, content, mediaType); + else { + if (mediaType.getSchema() != null || mediaType.getEncoding() != null || mediaType.getExample() != null || mediaType.getExamples() != null || mediaType.getExtensions() != null) + applyTypes(classTypes, methodTypes, content, mediaType); + } } if (content.size() == 0 && annotationContents.length != 1) { @@ -283,13 +291,14 @@ public static void removeAnnotationsToIgnore(Class... classes) { * Add encoding to media type. * * @param jsonViewAnnotation the json view annotation - * @param mediaType the media type - * @param encodings the encodings + * @param mediaType the media type + * @param encodings the encodings + * @param openapi31 the openapi 31 */ private static void addEncodingToMediaType(JsonView jsonViewAnnotation, MediaType mediaType, - io.swagger.v3.oas.annotations.media.Encoding[] encodings) { + io.swagger.v3.oas.annotations.media.Encoding[] encodings, boolean openapi31) { for (io.swagger.v3.oas.annotations.media.Encoding encoding : encodings) { - addEncodingToMediaType(mediaType, encoding, jsonViewAnnotation); + addEncodingToMediaType(mediaType, encoding, jsonViewAnnotation, openapi31); } } @@ -297,12 +306,13 @@ private static void addEncodingToMediaType(JsonView jsonViewAnnotation, MediaTyp * Add extension. * * @param annotationContent the annotation content - * @param mediaType the media type + * @param mediaType the media type + * @param openapi31 the openapi 31 */ private static void addExtension(io.swagger.v3.oas.annotations.media.Content annotationContent, - MediaType mediaType) { + MediaType mediaType, boolean openapi31) { if (annotationContent.extensions().length > 0) { - Map extensions = AnnotationsUtils.getExtensions(annotationContent.extensions()); + Map extensions = AnnotationsUtils.getExtensions(openapi31, annotationContent.extensions()); extensions.forEach(mediaType::addExtension); } } @@ -333,67 +343,73 @@ private static void setExamples(MediaType mediaType, ExampleObject[] examples) { /** * Gets media type. * - * @param schema the schema - * @param components the components + * @param schema the schema + * @param components the components * @param jsonViewAnnotation the json view annotation - * @param annotationContent the annotation content + * @param annotationContent the annotation content + * @param openapi31 the openapi 31 * @return the media type */ private static MediaType getMediaType(Schema schema, Components components, JsonView jsonViewAnnotation, - io.swagger.v3.oas.annotations.media.Content annotationContent) { + io.swagger.v3.oas.annotations.media.Content annotationContent, boolean openapi31) { MediaType mediaType = new MediaType(); - if (!annotationContent.schema().hidden()) { - if (components != null) { - try { - getSchema(annotationContent, components, jsonViewAnnotation).ifPresent(mediaType::setSchema); - if (annotationContent.schemaProperties().length > 0) { - if (mediaType.getSchema() == null) { - mediaType.schema(new Schema().type("object")); - } - Schema oSchema = mediaType.getSchema(); - for (SchemaProperty sp : annotationContent.schemaProperties()) { - Class schemaImplementation = sp.schema().implementation(); - boolean isArray = isArray(annotationContent); - getSchema(sp.schema(), sp.array(), isArray, schemaImplementation, components, jsonViewAnnotation) - .ifPresent(s -> { - if ("array".equals(oSchema.getType())) { - oSchema.getItems().addProperty(sp.name(), s); - } - else { - oSchema.addProperty(sp.name(), s); - } - }); - + if (annotationContent.schema().hidden()) { + return mediaType; + } + if (components == null) { + mediaType.setSchema(schema); + return mediaType; + } + try { + getSchema(annotationContent, components, jsonViewAnnotation, openapi31).ifPresent(mediaType::setSchema); + if (annotationContent.schemaProperties().length > 0) { + if (mediaType.getSchema() == null) { + mediaType.schema(new Schema().type("object")); + } + Schema oSchema = mediaType.getSchema(); + for (SchemaProperty sp : annotationContent.schemaProperties()) { + Class schemaImplementation = sp.schema().implementation(); + boolean isArray = false; + if (schemaImplementation == Void.class) { + schemaImplementation = sp.array().schema().implementation(); + if (schemaImplementation != Void.class) { + isArray = true; } } - if ( - hasSchemaAnnotation(annotationContent.additionalPropertiesSchema()) && - mediaType.getSchema() != null && - !Boolean.TRUE.equals(mediaType.getSchema().getAdditionalProperties()) && - !Boolean.FALSE.equals(mediaType.getSchema().getAdditionalProperties())) { - getSchemaFromAnnotation(annotationContent.additionalPropertiesSchema(), components, jsonViewAnnotation) - .ifPresent(s -> { - if ("array".equals(mediaType.getSchema().getType())) { - mediaType.getSchema().getItems().additionalProperties(s); - } - else { - mediaType.getSchema().additionalProperties(s); - } - } - ); - } - } - catch (Exception e) { - if (isArray(annotationContent)) - mediaType.setSchema(new ArraySchema().items(new StringSchema())); - else - mediaType.setSchema(new StringSchema()); + getSchema(sp.schema(), sp.array(), isArray, schemaImplementation, components, jsonViewAnnotation, openapi31) + .ifPresent(s -> { + if ("array".equals(oSchema.getType())) { + oSchema.getItems().addProperty(sp.name(), s); + } + else { + oSchema.addProperty(sp.name(), s); + } + }); } } - else { - mediaType.setSchema(schema); + if ( + hasSchemaAnnotation(annotationContent.additionalPropertiesSchema()) && + mediaType.getSchema() != null && + !Boolean.TRUE.equals(mediaType.getSchema().getAdditionalProperties()) && + !Boolean.FALSE.equals(mediaType.getSchema().getAdditionalProperties())) { + getSchemaFromAnnotation(annotationContent.additionalPropertiesSchema(), components, jsonViewAnnotation, openapi31) + .ifPresent(s -> { + if ("array".equals(mediaType.getSchema().getType())) { + mediaType.getSchema().getItems().additionalProperties(s); + } + else { + mediaType.getSchema().additionalProperties(s); + } + } + ); } } + catch (Exception e) { + if (isArray(annotationContent)) + mediaType.setSchema(new ArraySchema().items(new StringSchema())); + else + mediaType.setSchema(new StringSchema()); + } return mediaType; } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java index 2b3cb3042..af666c568 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2023 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. * * * * - * * * * 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. * * * * * * @@ -22,10 +24,13 @@ package org.springdoc.core; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; -import org.springdoc.core.SpringDocConfigProperties.ModelConverters.SortConverter; +import io.swagger.v3.oas.models.SpecVersion; +import org.springdoc.core.SpringDocConfigProperties.ApiDocs.OpenApiVersion; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -105,7 +110,7 @@ public class SpringDocConfigProperties { /** * The Group configs. */ - private List groupConfigs = new ArrayList<>(); + private Set groupConfigs = new HashSet<>(); /** * The Auto tag classes. @@ -162,6 +167,11 @@ public class SpringDocConfigProperties { */ private boolean preLoadingEnabled; + /** + * locale list to pre-loading + */ + private List preLoadingLocales; + /** * If set to true, exposes the swagger-ui on the actuator management port. */ @@ -175,22 +185,57 @@ public class SpringDocConfigProperties { /** * The Show spring cloud functions. */ - private boolean showSpringCloudFunctions; + private boolean showSpringCloudFunctions = true; /** * The param default flatten */ private boolean defaultFlatParamObject; + /** + * The model Converters + */ + private ModelConverters modelConverters = new ModelConverters(); + + /** + * The Enable groovy. + */ + private boolean enableGroovy = true; + + /** + * The Enable javadoc. + */ + private boolean enableJavadoc = true; + + /** + * The Enable spring security. + */ + private boolean enableSpringSecurity = true; + + /** + * The Enable kotlin. + */ + private boolean enableKotlin = true; + + /** + * The Enable hateoas. + */ + private boolean enableHateoas = true; + + /** + * The Enable hateoas. + */ + private boolean enableDataRest = true; + /** * convert query param to form data when consumes is multipart/form-data */ private boolean defaultSupportFormData; /** - * The model Converters + * The Show oauth 2 endpoint. */ - private ModelConverters modelConverters = new ModelConverters(); + private boolean showOauth2Endpoints; /** * The Sort converter. @@ -202,11 +247,6 @@ public class SpringDocConfigProperties { */ private boolean nullableRequestParameterEnabled; - /** - * The Show oauth2 endpoints. - */ - private boolean showOauth2Endpoints; - /** * Gets override with generic response. * @@ -216,6 +256,15 @@ public Boolean getOverrideWithGenericResponse() { return overrideWithGenericResponse; } + /** + * Sets override with generic response. + * + * @param overrideWithGenericResponse the override with generic response + */ + public void setOverrideWithGenericResponse(Boolean overrideWithGenericResponse) { + this.overrideWithGenericResponse = overrideWithGenericResponse; + } + /** * Is nullable request parameter enabled boolean. * @@ -252,6 +301,24 @@ public void setDefaultSupportFormData(boolean defaultSupportFormData) { this.defaultSupportFormData = defaultSupportFormData; } + /** + * Is default flat param object boolean. + * + * @return the boolean + */ + public boolean isDefaultFlatParamObject() { + return defaultFlatParamObject; + } + + /** + * Sets default flat param object. + * + * @param defaultFlatParamObject the default flat param object + */ + public void setDefaultFlatParamObject(boolean defaultFlatParamObject) { + this.defaultFlatParamObject = defaultFlatParamObject; + } + /** * Gets sort converter. * @@ -271,39 +338,129 @@ public void setSortConverter(SortConverter sortConverter) { } /** - * Is show spring cloud functions boolean. + * Is enable data rest boolean. * * @return the boolean */ - public boolean isShowSpringCloudFunctions() { - return showSpringCloudFunctions; + public boolean isEnableDataRest() { + return enableDataRest; } /** - * Sets show spring cloud functions. + * Sets enable data rest. * - * @param showSpringCloudFunctions the show spring cloud functions + * @param enableDataRest the enable data rest */ - public void setShowSpringCloudFunctions(boolean showSpringCloudFunctions) { - this.showSpringCloudFunctions = showSpringCloudFunctions; + public void setEnableDataRest(boolean enableDataRest) { + this.enableDataRest = enableDataRest; } /** - * Is default flat param object + * Is enable hateoas boolean. * * @return the boolean */ - public boolean isDefaultFlatParamObject() { - return defaultFlatParamObject; + public boolean isEnableHateoas() { + return enableHateoas; } /** - * Sets default flat param object. + * Sets enable hateoas. * - * @param defaultFlatParamObject the default flat param object + * @param enableHateoas the enable hateoas */ - public void setDefaultFlatParamObject(boolean defaultFlatParamObject) { - this.defaultFlatParamObject = defaultFlatParamObject; + public void setEnableHateoas(boolean enableHateoas) { + this.enableHateoas = enableHateoas; + } + + /** + * Is enable kotlin boolean. + * + * @return the boolean + */ + public boolean isEnableKotlin() { + return enableKotlin; + } + + /** + * Sets enable kotlin. + * + * @param enableKotlin the enable kotlin + */ + public void setEnableKotlin(boolean enableKotlin) { + this.enableKotlin = enableKotlin; + } + + /** + * Is enable spring security boolean. + * + * @return the boolean + */ + public boolean isEnableSpringSecurity() { + return enableSpringSecurity; + } + + /** + * Sets enable spring security. + * + * @param enableSpringSecurity the enable spring security + */ + public void setEnableSpringSecurity(boolean enableSpringSecurity) { + this.enableSpringSecurity = enableSpringSecurity; + } + + /** + * Is enable javadoc boolean. + * + * @return the boolean + */ + public boolean isEnableJavadoc() { + return enableJavadoc; + } + + /** + * Sets enable javadoc. + * + * @param enableJavadoc the enable javadoc + */ + public void setEnableJavadoc(boolean enableJavadoc) { + this.enableJavadoc = enableJavadoc; + } + + /** + * Is enable groovy boolean. + * + * @return the boolean + */ + public boolean isEnableGroovy() { + return enableGroovy; + } + + /** + * Sets enable groovy. + * + * @param enableGroovy the enable groovy + */ + public void setEnableGroovy(boolean enableGroovy) { + this.enableGroovy = enableGroovy; + } + + /** + * Is show spring cloud functions boolean. + * + * @return the boolean + */ + public boolean isShowSpringCloudFunctions() { + return showSpringCloudFunctions; + } + + /** + * Sets show spring cloud functions. + * + * @param showSpringCloudFunctions the show spring cloud functions + */ + public void setShowSpringCloudFunctions(boolean showSpringCloudFunctions) { + this.showSpringCloudFunctions = showSpringCloudFunctions; } /** @@ -627,7 +784,7 @@ public boolean isCacheDisabled() { * * @return the group configs */ - public List getGroupConfigs() { + public Set getGroupConfigs() { return groupConfigs; } @@ -636,7 +793,7 @@ public List getGroupConfigs() { * * @param groupConfigs the group configs */ - public void setGroupConfigs(List groupConfigs) { + public void setGroupConfigs(Set groupConfigs) { this.groupConfigs = groupConfigs; } @@ -699,7 +856,7 @@ public boolean isOverrideWithGenericResponse() { * * @param overrideWithGenericResponse the override with generic response */ - public void setOverrideWithGenericResponse(Boolean overrideWithGenericResponse) { + public void setOverrideWithGenericResponse(boolean overrideWithGenericResponse) { this.overrideWithGenericResponse = overrideWithGenericResponse; } @@ -797,14 +954,32 @@ public boolean isPreLoadingEnabled() { } /** - * Sets pre loading enabled. + * locale list to pre-loading. * - * @param preLoadingEnabled the pre loading enabled + * @return the Locales + */ + public List getPreLoadingLocales() { + return preLoadingLocales; + } + + /** + * Sets locale list to pre-loading. + * + * @param preLoadingEnabled the Locales */ public void setPreLoadingEnabled(boolean preLoadingEnabled) { this.preLoadingEnabled = preLoadingEnabled; } + /** + * Sets pre loading locales. + * + * @param preLoadingLocales the pre loading locales + */ + public void setPreLoadingLocales(List preLoadingLocales) { + this.preLoadingLocales = preLoadingLocales; + } + /** * The type Model converters. * @@ -882,37 +1057,6 @@ public void setPolymorphicConverter(PolymorphicConverter polymorphicConverter) { this.polymorphicConverter = polymorphicConverter; } - /** - * The type Sort converter. - * - * @author daniel -shuy - */ - public static class SortConverter { - - /** - * The Enabled. - */ - private boolean enabled; - - /** - * Is enabled boolean. - * - * @return the boolean - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Sets enabled. - * - * @param enabled the enabled - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - } - /** * The type Deprecating converter. * @@ -1006,6 +1150,37 @@ public void setEnabled(boolean enabled) { } } + /** + * The type Sort converter. + * + * @author daniel -shuy + */ + public static class SortConverter { + + /** + * The Enabled. + */ + private boolean enabled; + + /** + * Is enabled boolean. + * + * @return the boolean + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets enabled. + * + * @param enabled the enabled + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + /** * The type Webjars. * @@ -1057,6 +1232,11 @@ public static class ApiDocs { */ private boolean resolveSchemaProperties; + /** + * The Resolve extensions properties. + */ + private boolean resolveExtensionsProperties; + /** * The Groups. */ @@ -1193,8 +1373,20 @@ public String getVersion() { return version; } } - } + public boolean isResolveExtensionsProperties() { + return resolveExtensionsProperties; + } + + /** + * Sets resolve extensions properties. + * + * @param resolveExtensionsProperties the resolve extensions properties + */ + public void setResolveExtensionsProperties(boolean resolveExtensionsProperties) { + this.resolveExtensionsProperties = resolveExtensionsProperties; + } + } /** * The type Groups. @@ -1318,15 +1510,15 @@ public GroupConfig() { /** * Instantiates a new Group config. * - * @param group the group - * @param pathsToMatch the paths to match - * @param packagesToScan the packages to scan + * @param group the group + * @param pathsToMatch the paths to match + * @param packagesToScan the packages to scan * @param packagesToExclude the packages to exclude - * @param pathsToExclude the paths to exclude - * @param producesToMatch the produces to match - * @param consumesToMatch the consumes to match - * @param headersToMatch the headers to match - * @param displayName the display name + * @param pathsToExclude the paths to exclude + * @param producesToMatch the produces to match + * @param consumesToMatch the consumes to match + * @param headersToMatch the headers to match + * @param displayName the display name */ public GroupConfig(String group, List pathsToMatch, List packagesToScan, List packagesToExclude, List pathsToExclude, @@ -1504,13 +1696,59 @@ public String getDisplayName() { public void setDisplayName(String displayName) { this.displayName = displayName; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GroupConfig that = (GroupConfig) o; + return Objects.equals(group, that.group); + } + + @Override + public int hashCode() { + return Objects.hash(group); + } } + + /** + * Is show oauth 2 endpoints boolean. + * + * @return the boolean + */ public boolean isShowOauth2Endpoints() { return showOauth2Endpoints; } - public void setShowOauth2Endpoints(boolean showOauth2Endpoint) { - this.showOauth2Endpoints = showOauth2Endpoint; + /** + * Sets show oauth 2 endpoints. + * + * @param showOauth2Endpoints the show oauth 2 endpoints + */ + public void setShowOauth2Endpoints(boolean showOauth2Endpoints) { + this.showOauth2Endpoints = showOauth2Endpoints; + } + + /** + * Gets spec version. + * + * @return the spec version + */ + public SpecVersion getSpecVersion() { + if (apiDocs.getVersion() == OpenApiVersion.OPENAPI_3_1) + return SpecVersion.V31; + return SpecVersion.V30; + } + + /** + * Is openapi 31 boolean. + * + * @return the boolean + */ + public boolean isOpenapi31() { + if (apiDocs.getVersion() == OpenApiVersion.OPENAPI_3_1) + return true; + return false; } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java index df90236fe..4344c844f 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfiguration.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -49,6 +51,7 @@ import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter; import org.springdoc.core.converters.SortOpenAPIConverter; import org.springdoc.core.converters.WebFluxSupportConverter; +import org.springdoc.core.converters.models.SortObject; import org.springdoc.core.customizers.ActuatorOpenApiCustomizer; import org.springdoc.core.customizers.ActuatorOperationCustomizer; import org.springdoc.core.customizers.DataRestDelegatingMethodParameterCustomizer; @@ -284,13 +287,14 @@ OpenAPIService openAPIBuilder(Optional openAPI, /** * Model converter registrar model converter registrar. * - * @param modelConverters the model converters + * @param modelConverters the model converters + * @param springDocConfigProperties the spring doc config properties * @return the model converter registrar */ @Bean @Lazy(false) - ModelConverterRegistrar modelConverterRegistrar(Optional> modelConverters) { - return new ModelConverterRegistrar(modelConverters.orElse(Collections.emptyList())); + ModelConverterRegistrar modelConverterRegistrar(Optional> modelConverters, SpringDocConfigProperties springDocConfigProperties) { + return new ModelConverterRegistrar(modelConverters.orElse(Collections.emptyList()), springDocConfigProperties); } /** @@ -334,8 +338,8 @@ PropertyResolverUtils propertyResolverUtils(ConfigurableBeanFactory factory, Mes @Bean @ConditionalOnMissingBean @Lazy(false) - RequestBodyService requestBodyBuilder(GenericParameterService parameterBuilder) { - return new RequestBodyService(parameterBuilder); + RequestBodyService requestBodyBuilder(GenericParameterService parameterBuilder, PropertyResolverUtils propertyResolverUtils) { + return new RequestBodyService(parameterBuilder, propertyResolverUtils); } /** @@ -433,7 +437,7 @@ SpringDocProviders springDocProviders(Optional actuatorProvide @Bean @ConditionalOnMissingBean @Lazy(false) - ObjectMapperProvider springDocObjectMapperProvider(SpringDocConfigProperties springDocConfigProperties) { + ObjectMapperProvider springdocObjectMapperProvider(SpringDocConfigProperties springDocConfigProperties) { return new ObjectMapperProvider(springDocConfigProperties); } @@ -448,7 +452,7 @@ static class SpringDocActuatorConfiguration { /** * Springdoc bean factory post processor 3 bean factory post processor. * - * @param groupedOpenApis the grouped open apis + * @param groupedOpenApis the grouped open apis * @return the bean factory post processor */ @Bean @@ -462,13 +466,14 @@ static BeanFactoryPostProcessor springdocBeanFactoryPostProcessor3(List clazz) { ConverterUtils.removeJavaTypeToIgnore(clazz); return this; } + + /** + * Is valid path boolean. + * + * @param path the path + * @return the boolean + */ + public static boolean isValidPath(String path) { + if (StringUtils.isNotBlank(path) && !path.equals("/")) + return true; + return false; + } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringdocActuatorBeanFactoryConfigurer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringdocActuatorBeanFactoryConfigurer.java index b8c93cfa4..0da8789be 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringdocActuatorBeanFactoryConfigurer.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SpringdocActuatorBeanFactoryConfigurer.java @@ -39,6 +39,7 @@ import static org.springdoc.core.Constants.DEFAULT_GROUP_NAME; import static org.springdoc.core.Constants.HEALTH_PATTERN; import static org.springdoc.core.Constants.MANAGEMENT_ENDPOINTS_WEB; +import static org.springdoc.core.Constants.SPRINGDOC_PREFIX; /** * The type Springdoc bean factory configurer. @@ -54,7 +55,7 @@ public class SpringdocActuatorBeanFactoryConfigurer extends SpringdocBeanFactory /** * Instantiates a new Springdoc actuator bean factory configurer. * - * @param groupedOpenApis the grouped open apis + * @param groupedOpenApis the grouped open apis */ public SpringdocActuatorBeanFactoryConfigurer(List groupedOpenApis) { this.groupedOpenApis = groupedOpenApis; @@ -64,14 +65,17 @@ public SpringdocActuatorBeanFactoryConfigurer(List groupedOpenAp public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { final BindResult result = Binder.get(environment) .bind(MANAGEMENT_ENDPOINTS_WEB, WebEndpointProperties.class); - if (result.isBound()) { + final BindResult springDocConfigPropertiesBindResult = Binder.get(environment) + .bind(SPRINGDOC_PREFIX, SpringDocConfigProperties.class); + + if (result.isBound() && springDocConfigPropertiesBindResult.isBound()) { WebEndpointProperties webEndpointProperties = result.get(); - + SpringDocConfigProperties springDocConfigProperties = springDocConfigPropertiesBindResult.get(); List newGroups = new ArrayList<>(); ActuatorOpenApiCustomizer actuatorOpenApiCustomiser = new ActuatorOpenApiCustomizer(webEndpointProperties); beanFactory.registerSingleton("actuatorOpenApiCustomiser", actuatorOpenApiCustomiser); - ActuatorOperationCustomizer actuatorCustomizer = new ActuatorOperationCustomizer(); + ActuatorOperationCustomizer actuatorCustomizer = new ActuatorOperationCustomizer(springDocConfigProperties); beanFactory.registerSingleton("actuatorCustomizer", actuatorCustomizer); GroupedOpenApi actuatorGroup = GroupedOpenApi.builder().group(ACTUATOR_DEFAULT_GROUP) diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/SwaggerUiConfigParameters.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/SwaggerUiConfigParameters.java index ad57bc2c2..531b5bd98 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/SwaggerUiConfigParameters.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/SwaggerUiConfigParameters.java @@ -23,10 +23,12 @@ package org.springdoc.core; import java.net.URL; +import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.SortedMap; import java.util.TreeMap; import java.util.stream.Collectors; @@ -255,7 +257,9 @@ public boolean isValidUrl(String url) { * @return the config parameters */ public Map getConfigParameters() { - final Map params = new TreeMap<>(); + final TreeMap treeMap = new TreeMap<>(); + SortedMap params = Collections.synchronizedSortedMap(treeMap); + // empty-string prevents swagger-ui default validation params.put(VALIDATOR_URL_PROPERTY, validatorUrl != null ? validatorUrl : ""); SpringDocPropertiesUtils.put(CONFIG_URL_PROPERTY, configUrl, params); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/ModelConverterRegistrar.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/ModelConverterRegistrar.java index 64b765d07..30aa49651 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/ModelConverterRegistrar.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/ModelConverterRegistrar.java @@ -31,6 +31,7 @@ import org.apache.commons.lang3.reflect.FieldUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springdoc.core.SpringDocConfigProperties; /** * Wrapper for model converters to only register converters once @@ -41,7 +42,7 @@ public class ModelConverterRegistrar { /** * The constant modelConvertersInstance. */ - private static final ModelConverters modelConvertersInstance = ModelConverters.getInstance(); + private final ModelConverters modelConvertersInstance; /** * The constant LOGGER. @@ -51,9 +52,11 @@ public class ModelConverterRegistrar { /** * Instantiates a new Model converter registrar. * - * @param modelConverters spring registered model converter beans which have to be registered in {@link ModelConverters} instance + * @param modelConverters spring registered model converter beans which have to be registered in {@link ModelConverters} instance + * @param springDocConfigProperties the spring doc config properties */ - public ModelConverterRegistrar(List modelConverters) { + public ModelConverterRegistrar(List modelConverters, SpringDocConfigProperties springDocConfigProperties) { + modelConvertersInstance = ModelConverters.getInstance(springDocConfigProperties.isOpenapi31()); for (ModelConverter modelConverter : modelConverters) { Optional registeredConverterOptional = getRegisteredConverterSameAs(modelConverter); registeredConverterOptional.ifPresent(modelConvertersInstance::removeConverter); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/models/SortObject.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/models/SortObject.java new file mode 100644 index 000000000..c1b851270 --- /dev/null +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/converters/models/SortObject.java @@ -0,0 +1,67 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 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 org.springdoc.core.converters.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * The type Sort response. + * @author bnasslahsen + */ +@ArraySchema(arraySchema = @Schema(implementation = SortObject.class)) +public class SortObject { + + /** + * The Direction. + */ + @JsonProperty + private String direction; + + /** + * The Null handling. + */ + @JsonProperty + private String nullHandling; + + /** + * The Ascending. + */ + @JsonProperty + private boolean ascending; + + /** + * The Property. + */ + @JsonProperty + private String property; + + /** + * The Ignore case. + */ + @JsonProperty + private boolean ignoreCase; + +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java index df7f515f7..c49f85c17 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/ActuatorOperationCustomizer.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -36,6 +38,7 @@ import org.apache.commons.lang3.reflect.FieldUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springdoc.core.SpringDocConfigProperties; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredOperation; @@ -72,6 +75,21 @@ public class ActuatorOperationCustomizer implements GlobalOperationCustomizer { */ private static final Pattern pattern = Pattern.compile(".*'([^']*)'.*"); + /** + * The Spring doc config properties. + */ + private final SpringDocConfigProperties springDocConfigProperties; + + + /** + * Instantiates a new Actuator operation customizer. + * + * @param springDocConfigProperties the spring doc config properties + */ + public ActuatorOperationCustomizer(SpringDocConfigProperties springDocConfigProperties) { + this.springDocConfigProperties = springDocConfigProperties; + } + @Override public Operation customize(Operation operation, HandlerMethod handlerMethod) { if (operation.getTags() != null && operation.getTags().contains(getTag().getName())) { @@ -88,7 +106,7 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { for (OperationParameter operationParameter : operationMethod.getParameters()) { Field parameterField = FieldUtils.getDeclaredField(operationParameter.getClass(), PARAMETER, true); Parameter parameter = (Parameter) parameterField.get(operationParameter); - Schema schema = AnnotationsUtils.resolveSchemaFromType(parameter.getType(), null, null); + Schema schema = AnnotationsUtils.resolveSchemaFromType(parameter.getType(), null, null, springDocConfigProperties.isOpenapi31()); if (parameter.getAnnotation(Selector.class) == null) { operation.setRequestBody(new RequestBody() .content(new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, new MediaType().schema(schema)))); @@ -108,10 +126,8 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { while (matcher.find()) { operationId = matcher.group(1); } - if (operation.getSummary() == null && !summary.contains("$")) operation.setSummary(summary); - operation.setOperationId(operationId); } return operation; diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/DataRestDelegatingMethodParameterCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/DataRestDelegatingMethodParameterCustomizer.java index a54b4f50b..5a66d2358 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/DataRestDelegatingMethodParameterCustomizer.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/DataRestDelegatingMethodParameterCustomizer.java @@ -35,12 +35,14 @@ import io.swagger.v3.core.util.ObjectMapperFactory; import io.swagger.v3.oas.annotations.ExternalDocumentation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.StringToClassMapItem; 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.DependentRequired; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; @@ -188,209 +190,366 @@ public boolean allowReserved() { public Schema schema() { return new Schema() { + private Schema parameterSchema = parameter.schema(); + @Override public Class annotationType() { - return parameter.schema().annotationType(); + return parameterSchema.annotationType(); } @Override public Class implementation() { - return parameter.schema().implementation(); + return parameterSchema.implementation(); } @Override public Class not() { - return parameter.schema().not(); + return parameterSchema.not(); } @Override public Class[] oneOf() { - return parameter.schema().oneOf(); + return parameterSchema.oneOf(); } @Override public Class[] anyOf() { - return parameter.schema().anyOf(); + return parameterSchema.anyOf(); } @Override public Class[] allOf() { - return parameter.schema().allOf(); + return parameterSchema.allOf(); } @Override public String name() { - return parameter.schema().name(); + return parameterSchema.name(); } @Override public String title() { - return parameter.schema().title(); + return parameterSchema.title(); } @Override public double multipleOf() { - return parameter.schema().multipleOf(); + return parameterSchema.multipleOf(); } @Override public String maximum() { - return parameter.schema().maximum(); + return parameterSchema.maximum(); } @Override public boolean exclusiveMaximum() { - return parameter.schema().exclusiveMaximum(); + return parameterSchema.exclusiveMaximum(); } @Override public String minimum() { - return parameter.schema().minimum(); + return parameterSchema.minimum(); } @Override public boolean exclusiveMinimum() { - return parameter.schema().exclusiveMaximum(); + return parameterSchema.exclusiveMaximum(); } @Override public int maxLength() { - return parameter.schema().maxLength(); + return parameterSchema.maxLength(); } @Override public int minLength() { - return parameter.schema().minLength(); + return parameterSchema.minLength(); } @Override public String pattern() { - return parameter.schema().pattern(); + return parameterSchema.pattern(); } @Override public int maxProperties() { - return parameter.schema().maxProperties(); + return parameterSchema.maxProperties(); } @Override public int minProperties() { - return parameter.schema().minProperties(); + return parameterSchema.minProperties(); } @Override public String[] requiredProperties() { - return parameter.schema().requiredProperties(); + return parameterSchema.requiredProperties(); } @Override public boolean required() { - return parameter.schema().required(); + return parameterSchema.required(); } @Override public RequiredMode requiredMode() { - return parameter.schema().requiredMode(); + return parameterSchema.requiredMode(); } @Override public String description() { - return parameter.schema().description(); + return parameterSchema.description(); } @Override public String format() { - return parameter.schema().format(); + return parameterSchema.format(); } @Override public String ref() { - return parameter.schema().ref(); + return parameterSchema.ref(); } @Override public boolean nullable() { - return parameter.schema().nullable(); + return parameterSchema.nullable(); } @Override public boolean readOnly() { - return AccessMode.READ_ONLY.equals(parameter.schema().accessMode()); + return AccessMode.READ_ONLY.equals(parameterSchema.accessMode()); } @Override public boolean writeOnly() { - return AccessMode.WRITE_ONLY.equals(parameter.schema().accessMode()); + return AccessMode.WRITE_ONLY.equals(parameterSchema.accessMode()); } @Override public AccessMode accessMode() { - return parameter.schema().accessMode(); + return parameterSchema.accessMode(); } @Override public String example() { - return parameter.schema().example(); + return parameterSchema.example(); } @Override public ExternalDocumentation externalDocs() { - return parameter.schema().externalDocs(); + return parameterSchema.externalDocs(); } @Override public boolean deprecated() { - return parameter.schema().deprecated(); + return parameterSchema.deprecated(); } @Override public String type() { - return parameter.schema().type(); + return parameterSchema.type(); } @Override public String[] allowableValues() { - return parameter.schema().allowableValues(); + return parameterSchema.allowableValues(); } @Override public String defaultValue() { - return getDefaultValue(parameterName, pageableDefault, parameter.schema().defaultValue()); + return getDefaultValue(parameterName, pageableDefault, parameterSchema.defaultValue()); } @Override public String discriminatorProperty() { - return parameter.schema().discriminatorProperty(); + return parameterSchema.discriminatorProperty(); } @Override public DiscriminatorMapping[] discriminatorMapping() { - return parameter.schema().discriminatorMapping(); + return parameterSchema.discriminatorMapping(); } @Override public boolean hidden() { - return parameter.schema().hidden(); + return parameterSchema.hidden(); } @Override public boolean enumAsRef() { - return parameter.schema().enumAsRef(); + return parameterSchema.enumAsRef(); } @Override public Class[] subTypes() { - return parameter.schema().subTypes(); + return parameterSchema.subTypes(); } @Override public Extension[] extensions() { - return parameter.schema().extensions(); + return parameterSchema.extensions(); + } + + @Override + public Class[] prefixItems() { + return parameterSchema.prefixItems(); + } + + @Override + public String[] types() { + return parameterSchema.types(); + } + + @Override + public int exclusiveMaximumValue() { + return parameterSchema.exclusiveMaximumValue(); + } + + @Override + public int exclusiveMinimumValue() { + return parameterSchema.exclusiveMinimumValue(); + } + + @Override + public Class contains() { + return parameterSchema.contains(); + } + + @Override + public String $id() { + return parameterSchema.$id(); + } + + @Override + public String $schema() { + return parameterSchema.$schema(); + } + + @Override + public String $anchor() { + return parameterSchema.$anchor(); + } + + @Override + public String $vocabulary() { + return parameterSchema.$vocabulary(); + } + + @Override + public String $dynamicAnchor() { + return parameterSchema.$dynamicAnchor(); + } + + @Override + public String contentEncoding() { + return parameterSchema.contentEncoding(); + } + + @Override + public String contentMediaType() { + return parameterSchema.contentMediaType(); + } + + @Override + public Class contentSchema() { + return parameterSchema.contentSchema(); + } + + @Override + public Class propertyNames() { + return parameterSchema.propertyNames(); + } + + @Override + public int maxContains() { + return parameterSchema.maxContains(); + } + + @Override + public int minContains() { + return parameterSchema.minContains(); + } + + @Override + public Class additionalItems() { + return parameterSchema.additionalItems(); + } + + @Override + public Class unevaluatedItems() { + return parameterSchema.unevaluatedItems(); + } + + @Override + public Class _if() { + return parameterSchema._if(); + } + + @Override + public Class _else() { + return parameterSchema._else(); + } + + @Override + public Class then() { + return parameterSchema.then(); + } + + @Override + public String $comment() { + return parameterSchema.$comment(); + } + + @Override + public Class[] exampleClasses() { + return parameterSchema.exampleClasses(); } @Override public AdditionalPropertiesValue additionalProperties() { - return parameter.schema().additionalProperties(); + return parameterSchema.additionalProperties(); + } + + @Override + public DependentRequired[] dependentRequiredMap() { + return parameterSchema.dependentRequiredMap(); + } + + @Override + public StringToClassMapItem[] dependentSchemas() { + return parameterSchema.dependentSchemas(); + } + + @Override + public StringToClassMapItem[] patternProperties() { + return parameterSchema.patternProperties(); + } + + @Override + public StringToClassMapItem[] properties() { + return parameterSchema.properties(); + } + + @Override + public Class unevaluatedProperties() { + return parameterSchema.unevaluatedProperties(); + } + + @Override + public Class additionalPropertiesSchema() { + return parameterSchema.additionalPropertiesSchema(); + } + + @Override + public String[] examples() { + return parameterSchema.examples(); + } + + @Override + public String _const() { + return parameterSchema._const(); } }; } @@ -404,6 +563,11 @@ public Class annotationType() { return arraySchema.annotationType(); } + @Override + public Schema items() { + return arraySchema.items(); + } + @Override public Schema schema() { return arraySchema.schema(); @@ -615,10 +779,165 @@ public Extension[] extensions() { return schema.extensions(); } + @Override + public Class[] prefixItems() { + return schema.prefixItems(); + } + + @Override + public String[] types() { + return schema.types(); + } + + @Override + public int exclusiveMaximumValue() { + return schema.exclusiveMaximumValue(); + } + + @Override + public int exclusiveMinimumValue() { + return schema.exclusiveMinimumValue(); + } + + @Override + public Class contains() { + return schema.contains(); + } + + @Override + public String $id() { + return schema.$id(); + } + + @Override + public String $schema() { + return schema.$schema(); + } + + @Override + public String $anchor() { + return schema.$anchor(); + } + + @Override + public String $vocabulary() { + return schema.$vocabulary(); + } + + @Override + public String $dynamicAnchor() { + return schema.$dynamicAnchor(); + } + + @Override + public String contentEncoding() { + return schema.contentEncoding(); + } + + @Override + public String contentMediaType() { + return schema.contentMediaType(); + } + + @Override + public Class contentSchema() { + return schema.contentSchema(); + } + + @Override + public Class propertyNames() { + return schema.propertyNames(); + } + + @Override + public int maxContains() { + return schema.maxContains(); + } + + @Override + public int minContains() { + return schema.minContains(); + } + + @Override + public Class additionalItems() { + return schema.additionalItems(); + } + + @Override + public Class unevaluatedItems() { + return schema.unevaluatedItems(); + } + + @Override + public Class _if() { + return schema._if(); + } + + @Override + public Class _else() { + return schema._else(); + } + + @Override + public Class then() { + return schema.then(); + } + + @Override + public String $comment() { + return schema.$comment(); + } + + @Override + public Class[] exampleClasses() { + return schema.exampleClasses(); + } + @Override public AdditionalPropertiesValue additionalProperties() { return schema.additionalProperties(); } + + @Override + public DependentRequired[] dependentRequiredMap() { + return schema.dependentRequiredMap(); + } + + @Override + public StringToClassMapItem[] dependentSchemas() { + return schema.dependentSchemas(); + } + + @Override + public StringToClassMapItem[] patternProperties() { + return schema.patternProperties(); + } + + @Override + public StringToClassMapItem[] properties() { + return schema.properties(); + } + + @Override + public Class unevaluatedProperties() { + return schema.unevaluatedProperties(); + } + + @Override + public Class additionalPropertiesSchema() { + return schema.additionalPropertiesSchema(); + } + + @Override + public String[] examples() { + return schema.examples(); + } + + @Override + public String _const() { + return schema._const(); + } }; } @@ -641,6 +960,31 @@ public boolean uniqueItems() { public Extension[] extensions() { return arraySchema.extensions(); } + + @Override + public Schema contains() { + return arraySchema.contains(); + } + + @Override + public int maxContains() { + return arraySchema.maxContains(); + } + + @Override + public int minContains() { + return arraySchema.minContains(); + } + + @Override + public Schema unevaluatedItems() { + return arraySchema.unevaluatedItems(); + } + + @Override + public Schema[] prefixItems() { + return arraySchema.prefixItems(); + } }; } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecPropertiesCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecPropertiesCustomizer.java new file mode 100644 index 000000000..11c88299a --- /dev/null +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecPropertiesCustomizer.java @@ -0,0 +1,211 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 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 org.springdoc.core.customizers; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.StringUtils; + +import org.springframework.core.env.PropertyResolver; +import org.springframework.util.CollectionUtils; + +import static org.springdoc.core.Constants.SPRINGDOC_SPEC_PROPERTIES_PREFIX; + + +/** + * Allows externalizing strings in generated openapi schema via properties that follow + * conventional naming similar or identical to openapi schema + *

+ * To set value of a string in schema, define an application property that matches the target node + * with springdoc.spec-properties prefix. + *

+ * Sample supported properties for api-info customization: + *

    + *
  • springdoc.spec-properties.info.title - to set title of api-info
  • + *
  • springdoc.spec-properties.info.description - to set description of api-info
  • + *
  • springdoc.spec-properties.info.version - to set version of api-info
  • + *
  • springdoc.spec-properties.info.termsOfService - to set terms of service of api-info
  • + *
+ *

+ * Sample supported properties for components customization: + *

    + *
  • springdoc.spec-properties.components.User.description - to set description of User model
  • + *
  • springdoc.spec-properties.components.User.properties.name.description - to set description of 'name' property
  • + *
  • springdoc.spec-properties.components.User.properties.name.example - to set example of 'name' property
  • + *
+ *

+ * Sample supported properties for paths/operationIds customization: + *

    + *
  • springdoc.spec-properties.paths.{operationId}.description - to set description of {operationId}
  • + *
  • springdoc.spec-properties.paths.{operationId}.summary - to set summary of {operationId}
  • + *
+ *

+ * Support for groped openapi customization is similar to the above, but with a group name prefix. + * E.g. + *

    + *
  • springdoc.spec-properties.{group-name}.info.title - to set title of api-info
  • + *
  • springdoc.spec-properties.{group-name}.components.User.description - to set description of User model
  • + *
  • springdoc.spec-properties.{group-name}.paths.{operationId}.description - to set description of {operationId}
  • + *
+ * + * @author Anton Tkachenko tkachenkoas@gmail.com + * @author bnasslahsen + */ +public class SpecPropertiesCustomizer implements GlobalOpenApiCustomizer { + + /** + * The Property resolver. + */ + private final PropertyResolver propertyResolver; + + /** + * The Property prefix. + */ + private final String propertyPrefix; + + /** + * Instantiates a new Spec properties customizer. + * + * @param resolverUtils the resolver utils + */ + public SpecPropertiesCustomizer(PropertyResolver resolverUtils) { + this.propertyResolver = resolverUtils; + this.propertyPrefix = SPRINGDOC_SPEC_PROPERTIES_PREFIX; + } + + /** + * Instantiates a new Spec properties customizer. + * + * @param propertyResolver the property resolver + * @param groupName the group name + */ + public SpecPropertiesCustomizer(PropertyResolver propertyResolver, String groupName) { + this.propertyResolver = propertyResolver; + this.propertyPrefix = SPRINGDOC_SPEC_PROPERTIES_PREFIX + groupName + "."; + } + + @Override + public void customise(OpenAPI openApi) { + setOperationInfoProperties(openApi); + setComponentsProperties(openApi); + setPathsProperties(openApi); + } + + /** + * Sets operation info properties. + * + * @param openApi the open api + */ + private void setOperationInfoProperties(OpenAPI openApi) { + if (openApi.getInfo() == null) { + openApi.setInfo(new Info()); + } + Info info = openApi.getInfo(); + resolveString(info::setTitle, "info.title"); + resolveString(info::setDescription, "info.description"); + resolveString(info::setVersion, "info.version"); + resolveString(info::setTermsOfService, "info.termsOfService"); + } + + /** + * Sets paths properties. + * + * @param openApi the open api + */ + private void setPathsProperties(OpenAPI openApi) { + Paths paths = openApi.getPaths(); + if (CollectionUtils.isEmpty(paths.values())) { + return; + } + for (PathItem pathItem : paths.values()) { + List operations = pathItem.readOperations(); + for (Operation operation : operations) { + String operationId = operation.getOperationId(); + String operationNode = MessageFormat.format("paths.{0}", operationId); + resolveString(operation::setDescription, operationNode + ".description"); + + resolveString(operation::setSummary, operationNode + ".summary"); + } + } + } + + /** + * Sets components properties. + * + * @param openApi the open api + */ + private void setComponentsProperties(OpenAPI openApi) { + Components components = openApi.getComponents(); + if (components == null || CollectionUtils.isEmpty(components.getSchemas())) { + return; + } + + for (Schema componentSchema : components.getSchemas().values()) { + // set component description + String schemaPropertyPrefix = MessageFormat.format("components.schemas.{0}", componentSchema.getName()); + resolveString(componentSchema::setDescription, schemaPropertyPrefix + ".description"); + Map properties = componentSchema.getProperties(); + + if (CollectionUtils.isEmpty(properties)) { + continue; + } + + for (Schema propSchema : properties.values()) { + String propertyNode = MessageFormat.format("components.schemas.{0}.properties.{1}", + componentSchema.getName(), propSchema.getName()); + + resolveString(propSchema::setDescription, propertyNode + ".description"); + resolveString(propSchema::setExample, propertyNode + ".example"); + } + } + } + + /** + * Resolve string. + * + * @param setter the setter + * @param node the node + */ + private void resolveString( + Consumer setter, String node + ) { + String nodeWithPrefix = propertyPrefix + node; + String value = propertyResolver.getProperty(nodeWithPrefix); + if (StringUtils.isNotBlank(value)) { + setter.accept(value); + } + } + +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java new file mode 100644 index 000000000..86dad0458 --- /dev/null +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/customizers/SpecificationStringPropertiesCustomizer.java @@ -0,0 +1,171 @@ +package org.springdoc.core.customizers; + +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2022 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. + * * * * + * * * + * * + * + */ + + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.StringUtils; + +import org.springframework.core.env.PropertyResolver; +import org.springframework.util.CollectionUtils; + +/** + * Allows externalizing strings in generated openapi schema via properties that follow + * conventional naming similar or identical to openapi schema + *

+ * To set value of a string in schema, define an application property that matches the target node + * with springdoc.specification-strings prefix. + *

+ * Sample supported properties for api-info customization: + *

    + *
  • springdoc.specification-strings.info.title - to set title of api-info
  • + *
  • springdoc.specification-strings.info.description - to set description of api-info
  • + *
  • springdoc.specification-strings.info.version - to set version of api-info
  • + *
  • springdoc.specification-strings.info.termsOfService - to set terms of service of api-info
  • + *
+ *

+ * Sample supported properties for components customization: + *

    + *
  • springdoc.specification-strings.components.User.description - to set description of User model
  • + *
  • springdoc.specification-strings.components.User.properties.name.description - to set description of 'name' property
  • + *
  • springdoc.specification-strings.components.User.properties.name.example - to set example of 'name' property
  • + *
+ *

+ * Sample supported properties for paths/operationIds customization: + *

    + *
  • springdoc.specification-strings.paths.{operationId}.description - to set description of {operationId}
  • + *
  • springdoc.specification-strings.paths.{operationId}.summary - to set summary of {operationId}
  • + *
+ *

+ * Support for groped openapi customization is similar to the above, but with a group name prefix. + * E.g. + *

    + *
  • springdoc.specification-strings.{group-name}.info.title - to set title of api-info
  • + *
  • springdoc.specification-strings.{group-name}.components.User.description - to set description of User model
  • + *
  • springdoc.specification-strings.{group-name}.paths.{operationId}.description - to set description of {operationId}
  • + *
+ * + * @author Anton Tkachenko tkachenkoas@gmail.com + */ +public class SpecificationStringPropertiesCustomizer implements GlobalOpenApiCustomizer { + + private static final String SPECIFICATION_STRINGS_PREFIX = "springdoc.specification-strings."; + + private final PropertyResolver propertyResolver; + private final String propertyPrefix; + + public SpecificationStringPropertiesCustomizer(PropertyResolver resolverUtils) { + this.propertyResolver = resolverUtils; + this.propertyPrefix = SPECIFICATION_STRINGS_PREFIX; + } + + public SpecificationStringPropertiesCustomizer(PropertyResolver propertyResolver, String groupName) { + this.propertyResolver = propertyResolver; + this.propertyPrefix = SPECIFICATION_STRINGS_PREFIX + groupName + "."; + } + + @Override + public void customise(OpenAPI openApi) { + setOperationInfoProperties(openApi); + setComponentsProperties(openApi); + setPathsProperties(openApi); + } + + private void setOperationInfoProperties(OpenAPI openApi) { + if (openApi.getInfo() == null) { + openApi.setInfo(new Info()); + } + Info info = openApi.getInfo(); + resolveString(info::setTitle, "info.title"); + resolveString(info::setDescription, "info.description"); + resolveString(info::setVersion, "info.version"); + resolveString(info::setTermsOfService, "info.termsOfService"); + } + + private void setPathsProperties(OpenAPI openApi) { + Paths paths = openApi.getPaths(); + if (CollectionUtils.isEmpty(paths.values())) { + return; + } + for (PathItem pathItem : paths.values()) { + List operations = pathItem.readOperations(); + for (Operation operation : operations) { + String operationId = operation.getOperationId(); + String operationNode = MessageFormat.format("paths.{0}", operationId); + resolveString(operation::setDescription, operationNode + ".description"); + + resolveString(operation::setSummary, operationNode + ".summary"); + } + } + } + + private void setComponentsProperties(OpenAPI openApi) { + Components components = openApi.getComponents(); + if (components == null || CollectionUtils.isEmpty(components.getSchemas())) { + return; + } + + for (Schema componentSchema : components.getSchemas().values()) { + // set component description + String schemaPropertyPrefix = MessageFormat.format("components.schemas.{0}", componentSchema.getName()); + resolveString(componentSchema::setDescription, schemaPropertyPrefix + ".description"); + Map properties = componentSchema.getProperties(); + + if (CollectionUtils.isEmpty(properties)) { + continue; + } + + for (Schema propSchema : properties.values()) { + String propertyNode = MessageFormat.format("components.schemas.{0}.properties.{1}", + componentSchema.getName(), propSchema.getName()); + + resolveString(propSchema::setDescription, propertyNode + ".description"); + resolveString(propSchema::setExample, propertyNode + ".example"); + } + } + } + + private void resolveString( + Consumer setter, String node + ) { + String nodeWithPrefix = propertyPrefix + node; + String value = propertyResolver.getProperty(nodeWithPrefix); + if (StringUtils.isNotBlank(value)) { + setter.accept(value); + } + } + +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/RouterFunctionData.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/RouterFunctionData.java index 86662f0ea..86df31958 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/RouterFunctionData.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/RouterFunctionData.java @@ -296,6 +296,9 @@ private RequestMethod getRequestMethod(HttpMethod httpMethod) { case OPTIONS: requestMethod = RequestMethod.OPTIONS; break; + case TRACE: + requestMethod = RequestMethod.TRACE; + break; default: // Do nothing here break; diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/arrayschema/Builder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/arrayschema/Builder.java index 86837bd39..d6d8882f7 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/arrayschema/Builder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/arrayschema/Builder.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -31,47 +33,67 @@ /** * The type Array schema builder. + * * @author bnasslahsen */ public class Builder { /** * The schema of the items in the array - * */ private Schema schema = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); /** * Allows to define the properties to be resolved into properties of the schema of type `array` (not the ones of the * `items` of such schema which are defined in schema}. - * */ private Schema arraySchema = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); /** * sets the maximum number of items in an array. Ignored if value is Integer.MIN_VALUE. - * */ private int maxItems = Integer.MIN_VALUE; /** * sets the minimum number of items in an array. Ignored if value is Integer.MAX_VALUE. - * */ private int minItems = Integer.MAX_VALUE; /** * determines whether an array of items will be unique - * */ private boolean uniqueItems; /** * The list of optional extensions - * */ private Extension[] extensions = {}; + /** + * The Contains. + */ + private Schema contains = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The Max contain. + */ + private int maxContains= 0; + + /** + * The Min contains. + */ + private int minContains= 0; + + /** + * The Unevaluated items. + */ + private Schema unevaluatedItems= org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The Prefix items. + */ + private Schema[] prefixItems = {}; + /** * Instantiates a new Array schema builder. */ @@ -165,6 +187,11 @@ public Class annotationType() { return null; } + @Override + public Schema items() { + return null; + } + @Override public Schema schema() { return schema; @@ -194,6 +221,31 @@ public boolean uniqueItems() { public Extension[] extensions() { return extensions; } + + @Override + public Schema contains() { + return contains; + } + + @Override + public int maxContains() { + return maxContains; + } + + @Override + public int minContains() { + return minContains; + } + + @Override + public Schema unevaluatedItems() { + return unevaluatedItems; + } + + @Override + public Schema[] prefixItems() { + return prefixItems; + } }; } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/content/Builder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/content/Builder.java index 49baed531..24727f631 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/content/Builder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/content/Builder.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -27,6 +29,7 @@ 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.DependentSchema; import io.swagger.v3.oas.annotations.media.Encoding; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; @@ -35,59 +38,107 @@ /** * The type Content builder. + * * @author bnasslahsen */ public class Builder { /** * The schema properties defined for schema provided in @Schema - * */ private final Schema additionalPropertiesSchema = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + /** + * The Additional properties array schema. + */ + private final ArraySchema additionalPropertiesArraySchema = org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder().build(); + /** * The schema properties defined for schema provided in @Schema - * */ private final SchemaProperty[] schemaProperties = {}; /** * The media type that this object applies to. - * */ private String mediaType = ""; /** * An array of examples used to show the use of the associated schema. - * */ private ExampleObject[] examples = {}; /** * The schema defining the type used for the content. - * */ private Schema schema = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); /** * The schema of the array that defines the type used for the content. - * */ private ArraySchema array = org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder().build(); /** * An array of encodings * The key, being the property name, MUST exist in the schema as a property. - * */ private Encoding[] encodings = {}; /** * The list of optional extensions - * */ private Extension[] extensions = {}; + /** + * The Dependent schemas. + */ + private DependentSchema[] dependentSchemas = {}; + + /** + * The Content schem. + */ + private Schema contentSchem = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The Property names. + */ + private Schema propertyNames = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The If. + */ + private Schema _if = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The Then. + */ + private Schema _then = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The Else. + */ + private Schema _else = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The Not. + */ + private Schema not = org.springdoc.core.fn.builders.schema.Builder.schemaBuilder().build(); + + /** + * The One of. + */ + private Schema[] oneOf = {}; + + /** + * The Any of. + */ + private Schema[] anyOf = {}; + + /** + * The All of. + */ + private Schema[] allOf ={}; + /** * Instantiates a new Content builder. */ @@ -207,6 +258,11 @@ public Schema additionalPropertiesSchema() { return additionalPropertiesSchema; } + @Override + public ArraySchema additionalPropertiesArraySchema() { + return additionalPropertiesArraySchema; + } + @Override public ArraySchema array() { return array; @@ -221,6 +277,56 @@ public Encoding[] encoding() { public Extension[] extensions() { return extensions; } + + @Override + public DependentSchema[] dependentSchemas() { + return new DependentSchema[0]; + } + + @Override + public Schema contentSchema() { + return contentSchem; + } + + @Override + public Schema propertyNames() { + return propertyNames; + } + + @Override + public Schema _if() { + return _if; + } + + @Override + public Schema _then() { + return _then; + } + + @Override + public Schema _else() { + return _else; + } + + @Override + public Schema not() { + return not; + } + + @Override + public Schema[] oneOf() { + return oneOf; + } + + @Override + public Schema[] anyOf() { + return anyOf; + } + + @Override + public Schema[] allOf() { + return allOf; + } }; } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/discriminatormapping/Builder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/discriminatormapping/Builder.java index a333e90bb..93ee0b3fc 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/discriminatormapping/Builder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/discriminatormapping/Builder.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -24,6 +26,7 @@ import java.lang.annotation.Annotation; +import io.swagger.v3.oas.annotations.extensions.Extension; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; /** @@ -44,6 +47,10 @@ public class Builder { */ private Class schema = Void.class; + /** + * The Extensions. + */ + private Extension[] extensions = {}; /** * Instantiates a new Discriminator mapping builder. @@ -104,6 +111,11 @@ public String value() { public Class schema() { return schema; } + + @Override + public Extension[] extensions() { + return extensions; + } }; } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/requestbody/Builder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/requestbody/Builder.java index 0e777473a..6c0bb9b8f 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/requestbody/Builder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/requestbody/Builder.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -66,6 +68,8 @@ public class Builder { */ private String ref = ""; + private boolean useParameterTypeSchema = false; + /** * Instantiates a new Request body builder. @@ -185,6 +189,11 @@ public Extension[] extensions() { public String ref() { return ref; } + + @Override + public boolean useParameterTypeSchema() { + return useParameterTypeSchema; + } }; } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/schema/Builder.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/schema/Builder.java index d77857c1d..6746a69a1 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/schema/Builder.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/fn/builders/schema/Builder.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -25,7 +27,9 @@ import java.lang.annotation.Annotation; import io.swagger.v3.oas.annotations.ExternalDocumentation; +import io.swagger.v3.oas.annotations.StringToClassMapItem; import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.media.DependentRequired; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.AccessMode; @@ -35,214 +39,175 @@ /** * The type Schema builder. + * * @author bnasslahsen */ public class Builder { /** * Provides a java class as implementation for this schema. When provided, additional information in the Schema annotation (except for type information) will augment the java class after introspection. - * */ private Class implementation = Void.class; /** * Provides a java class to be used to disallow matching properties. - * */ private Class not = Void.class; /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If more than one match the derived schemas, a validation error will occur. - * */ private Class[] oneOf = {}; /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If any match, the schema will be considered valid. - * */ private Class[] anyOf = {}; /** * Provides an array of java class implementations which can be used to describe multiple acceptable schemas. If all match, the schema will be considered valid - * */ private Class[] allOf = {}; /** * The name of the schema or property. - * */ private String name = ""; /** * A title to explain the purpose of the schema. - * */ private String title = ""; /** * Constrains a value such that when divided by the multipleOf, the remainder must be an integer. Ignored if the value is 0. - * */ private double multipleOf = 0; /** * Sets the maximum numeric value for a property. Ignored if the value is an empty string. - * */ private String maximum = ""; /** * if true, makes the maximum value exclusive, or a less-than criteria. - * */ private boolean exclusiveMaximum; /** * Sets the minimum numeric value for a property. Ignored if the value is an empty string or not a number. - * */ private String minimum = ""; /** * If true, makes the minimum value exclusive, or a greater-than criteria. - * */ private boolean exclusiveMinimum; /** * Sets the maximum length of a string value. Ignored if the value is negative. - * */ private int maxLength = Integer.MAX_VALUE; /** * Sets the minimum length of a string value. Ignored if the value is negative. - * */ private int minLength = 0; /** * A pattern that the value must satisfy. Ignored if the value is an empty string. - * */ private String pattern = ""; /** * Constrains the number of arbitrary properties when additionalProperties is defined. Ignored if value is 0. - * */ private int maxProperties = 0; /** * Constrains the number of arbitrary properties when additionalProperties is defined. Ignored if value is 0. - * */ private int minProperties = 0; /** * Allows multiple properties in an object to be marked as required. - * */ private String[] requiredProperties = {}; /** * Mandates that the annotated item is required or not. - * */ private boolean required; /** * A description of the schema. - * */ private String description = ""; /** * Provides an optional override for the format. If a consumer is unaware of the meaning of the format, they shall fall back to using the basic type without format. For example, if \"type: integer, format: int128\" were used to designate a very large integer, most consumers will not understand how to handle it, and fall back to simply \"type: integer\" - * */ private String format = ""; /** * References a schema definition in an external OpenAPI document. - * */ private String ref = ""; /** * If true, designates a value as possibly null. - * */ private boolean nullable; - /** - * The Required mode. - */ - private RequiredMode requiredMode = Schema.RequiredMode.AUTO; - /** * Allows to specify the access mode (AccessMode.READ_ONLY, READ_WRITE) - * * AccessMode.READ_ONLY: value will not be written to during a request but may be returned during a response. * AccessMode.WRITE_ONLY: value will only be written to during a request but not returned during a response. * AccessMode.READ_WRITE: value will be written to during a request and returned during a response. - * - * */ private AccessMode accessMode = io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO; /** * Provides an example of the schema. When associated with a specific media type, the example string shall be parsed by the consumer to be treated as an object or an array. - * */ private String example = ""; /** * Additional external documentation for this schema. - * */ private ExternalDocumentation externalDocs = org.springdoc.core.fn.builders.externaldocumentation.Builder.externalDocumentationBuilder().build(); /** * Specifies that a schema is deprecated and should be transitioned out of usage. - * */ private boolean deprecated; /** * Provides an override for the basic type of the schema. Must be a valid type per the OpenAPI Specification. - * */ private String type = ""; /** * Provides a list of allowable values. This field map to the enum property in the OAS schema. - * */ private String[] allowableValues = {}; /** * Provides a default value. - * */ private String defaultValue = ""; /** * Provides a discriminator property value. - * */ private String discriminatorProperty = ""; /** * Provides discriminator mapping values. - * */ private DiscriminatorMapping[] discriminatorMapping = {}; /** * Allows schema to be marked as hidden. - * */ private boolean hidden; @@ -250,7 +215,6 @@ public class Builder { * Allows enums to be resolved as a reference to a scheme added to components section. * * @since swagger -core 2.1.0 - * @return whether or not this must be resolved as a reference */ private boolean enumAsRef; @@ -261,21 +225,177 @@ public class Builder { /** * The list of optional extensions - * - * @return an optional array of extensions */ private Extension[] extensions = {}; /** * Allows to specify the additionalProperties value - * * AdditionalPropertiesValue.TRUE: set to TRUE * AdditionalPropertiesValue.FALSE: set to FALSE * AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION: resolve from @Content.additionalPropertiesSchema - * */ private AdditionalPropertiesValue additionalProperties = AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION; + /** + * The Required mode. + */ + private RequiredMode requiredMode = Schema.RequiredMode.AUTO; + + /** + * The Prefix items. + */ + private Class[] prefixItems = {}; + + /** + * The Types. + */ + private String[] types ={}; + + /** + * The Exclusive maximum value. + */ + private int exclusiveMaximumValue = 0; + + /** + * The Exclusive minimum value. + */ + private int exclusiveMinimumValue = 0; + + /** + * The Contains. + */ + private Class contains = Void.class; + + /** + * The Id. + */ + private String $id = ""; + + /** + * The Schema. + */ + private String $schema = ""; + + /** + * The Anchor. + */ + private String $anchor = ""; + + /** + * The Vocabulary. + */ + private String $vocabulary = ""; + + /** + * The Dynamic anchor. + */ + private String $dynamicAnchor = ""; + + /** + * The Content encoding. + */ + private String contentEncoding = ""; + + /** + * The Content media type. + */ + private String contentMediaType = ""; + + /** + * The Content schema. + */ + private Class contentSchema = Void.class; + + /** + * The Property names. + */ + private Class propertyNames = Void.class; + + /** + * The Max contains. + */ + private int maxContains = Integer.MAX_VALUE; + + /** + * The Min contains. + */ + private int minContains = 0; + + /** + * The Additional items. + */ + private Class additionalItems = Void.class; + + /** + * The Unevaluated items. + */ + private Class unevaluatedItems = Void.class; + + /** + * The If. + */ + private Class _if = Void.class; + + /** + * The Else. + */ + private Class _else = Void.class; + + /** + * The Then. + */ + private Class then = Void.class; + + /** + * The Comment. + */ + private String $comment = ""; + + /** + * The Example classes. + */ + private Class[] exampleClasses = {}; + + /** + * The Dependent required map. + */ + private DependentRequired[] dependentRequiredMap = {}; + + /** + * The Dependent schemas. + */ + private StringToClassMapItem[] dependentSchemas = {}; + + /** + * The Pattern properties. + */ + private StringToClassMapItem[] patternProperties = {}; + + /** + * The Properties. + */ + private StringToClassMapItem[] properties= {}; + + /** + * The Unevaluated properties. + */ + private Class unevaluatedProperties = Void.class; + + /** + * The Additional properties schema. + */ + private Class additionalPropertiesSchema = Void.class; + + /** + * The Examples. + */ + private String[] examples = {}; + + /** + * The Const. + */ + private String _const = ""; + /** * Instantiates a new Schema builder. */ @@ -916,10 +1036,165 @@ public Extension[] extensions() { return extensions; } + @Override + public Class[] prefixItems() { + return prefixItems; + } + + @Override + public String[] types() { + return types; + } + + @Override + public int exclusiveMaximumValue() { + return exclusiveMaximumValue; + } + + @Override + public int exclusiveMinimumValue() { + return exclusiveMinimumValue; + } + + @Override + public Class contains() { + return contains; + } + + @Override + public String $id() { + return $id; + } + + @Override + public String $schema() { + return $schema; + } + + @Override + public String $anchor() { + return $anchor; + } + + @Override + public String $vocabulary() { + return $vocabulary; + } + + @Override + public String $dynamicAnchor() { + return $dynamicAnchor; + } + + @Override + public String contentEncoding() { + return contentEncoding; + } + + @Override + public String contentMediaType() { + return contentMediaType; + } + + @Override + public Class contentSchema() { + return contentSchema; + } + + @Override + public Class propertyNames() { + return propertyNames; + } + + @Override + public int maxContains() { + return maxContains; + } + + @Override + public int minContains() { + return minContains; + } + + @Override + public Class additionalItems() { + return additionalItems; + } + + @Override + public Class unevaluatedItems() { + return unevaluatedItems; + } + + @Override + public Class _if() { + return _if; + } + + @Override + public Class _else() { + return _else; + } + + @Override + public Class then() { + return then; + } + + @Override + public String $comment() { + return $comment; + } + + @Override + public Class[] exampleClasses() { + return exampleClasses; + } + @Override public AdditionalPropertiesValue additionalProperties() { return additionalProperties; } + + @Override + public DependentRequired[] dependentRequiredMap() { + return dependentRequiredMap; + } + + @Override + public StringToClassMapItem[] dependentSchemas() { + return dependentSchemas; + } + + @Override + public StringToClassMapItem[] patternProperties() { + return patternProperties; + } + + @Override + public StringToClassMapItem[] properties() { + return properties; + } + + @Override + public Class unevaluatedProperties() { + return unevaluatedProperties; + } + + @Override + public Class additionalPropertiesSchema() { + return additionalPropertiesSchema; + } + + @Override + public String[] examples() { + return examples; + } + + @Override + public String _const() { + return _const; + } }; } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/SpringCloudFunctionProvider.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/SpringCloudFunctionProvider.java index 08614ee8e..1152d1eba 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/SpringCloudFunctionProvider.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/SpringCloudFunctionProvider.java @@ -208,7 +208,7 @@ private void getRouterOperationsCommon(String name, RequestMethod requestMethod, */ private void buildRequest(OpenAPI openAPI, String name, FunctionInvocationWrapper function, RequestMethod requestMethod, RouterOperation routerOperation) { Type paramType = function.getInputType(); - Schema schema = SpringDocAnnotationsUtils.extractSchema(openAPI.getComponents(), paramType, null, null); + Schema schema = SpringDocAnnotationsUtils.extractSchema(openAPI.getComponents(), paramType, null, null, openAPI.getSpecVersion()); if (GET.equals(requestMethod)) { Parameter parameter = new PathParameter().name(name).schema(schema); routerOperation.getOperationModel().addParametersItem(parameter); diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/WebConversionServiceProvider.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/WebConversionServiceProvider.java index 144c822aa..246cac776 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/WebConversionServiceProvider.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/providers/WebConversionServiceProvider.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; @@ -113,18 +114,20 @@ public Class getSpringConvertedType(Class clazz) { Field convertersField = FieldUtils.getDeclaredField(GenericConversionService.class, CONVERTERS, true); if (convertersField != null) { Object converters; - try { - converters = convertersField.get(formattingConversionService); - convertersField = FieldUtils.getDeclaredField(converters.getClass(), CONVERTERS, true); - Map springConverters = (Map) convertersField.get(converters); - Optional convertiblePairOptional = springConverters.keySet().stream().filter(convertiblePair -> convertiblePair.getTargetType().equals(clazz)).findAny(); - if (convertiblePairOptional.isPresent()) { - ConvertiblePair convertiblePair = convertiblePairOptional.get(); - result = convertiblePair.getSourceType(); + if (!AopUtils.isAopProxy(formattingConversionService)){ + try { + converters = convertersField.get(formattingConversionService); + convertersField = FieldUtils.getDeclaredField(converters.getClass(), CONVERTERS, true); + Map springConverters = (Map) convertersField.get(converters); + Optional convertiblePairOptional = springConverters.keySet().stream().filter(convertiblePair -> convertiblePair.getTargetType().equals(clazz)).findAny(); + if (convertiblePairOptional.isPresent()) { + ConvertiblePair convertiblePair = convertiblePairOptional.get(); + result = convertiblePair.getSourceType(); + } + } + catch (IllegalAccessException e) { + LOGGER.warn(e.getMessage()); } - } - catch (IllegalAccessException e) { - LOGGER.warn(e.getMessage()); } } return result; diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java b/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java index f5ca9202e..3135bbea7 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerIndexTransformer.java @@ -133,6 +133,16 @@ protected String overwriteSwaggerDefaultUrl(String html) { return html.replace(Constants.SWAGGER_UI_DEFAULT_URL, StringUtils.EMPTY); } + /** + * Setting the url configured with swagger ui properties + * + * @param html + * @return modifed html + */ + protected String setConfiguredApiDocsUrl(String html){ + return html.replace(Constants.SWAGGER_UI_DEFAULT_URL, swaggerUiConfig.getUrl()); + } + /** * Default transformations string. * @@ -165,6 +175,9 @@ else if (swaggerUiConfig.getCsrf().isUseSessionStorage()) if (swaggerUiConfig.isDisableSwaggerDefaultUrl()) html = overwriteSwaggerDefaultUrl(html); + if(StringUtils.isNotEmpty(swaggerUiConfig.getUrl()) && StringUtils.isEmpty(swaggerUiConfig.getConfigUrl())){ + html = setConfiguredApiDocsUrl(html); + } return html; } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerResourceResolver.java b/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerResourceResolver.java index 2c27d3e9e..37d327898 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerResourceResolver.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerResourceResolver.java @@ -1,6 +1,7 @@ package org.springdoc.ui; -import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import org.springdoc.core.SwaggerUiConfigProperties; @@ -30,46 +31,18 @@ public AbstractSwaggerResourceResolver(SwaggerUiConfigProperties swaggerUiConfig /** * Find web jar resource path string. * - * @param path the path + * @param pathStr the path * @return the string */ @Nullable - protected String findWebJarResourcePath(String path) { - String webjar = webjar(path); - if (webjar.length() > 0) { - String version = swaggerUiConfigProperties.getVersion(); - if (version != null) { - String partialPath = path(webjar, path); - return webjar + File.separator + version + File.separator + partialPath; - } - } - return null; + protected String findWebJarResourcePath(String pathStr) { + Path path = Paths.get(pathStr); + if (path.getNameCount() < 2) return null; + String version = swaggerUiConfigProperties.getVersion(); + if (version == null) return null; + Path first = path.getName(0); + Path rest = path.subpath(1, path.getNameCount()); + return first.resolve(version).resolve(rest).toString(); } - - /** - * Webjar string. - * - * @param path the path - * @return the string - */ - private String webjar(String path) { - int startOffset = (path.startsWith("/") ? 1 : 0); - int endOffset = path.indexOf('/', 1); - return endOffset != -1 ? path.substring(startOffset, endOffset) : path; - } - - - /** - * Path string. - * - * @param webjar the webjar - * @param path the path - * @return the string - */ - private String path(String webjar, String path) { - if (path.startsWith(webjar)) { - path = path.substring(webjar.length() + 1); - } - return path; - } -} \ No newline at end of file + +} diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerWelcome.java b/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerWelcome.java index a95739c92..089a11b6f 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerWelcome.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/ui/AbstractSwaggerWelcome.java @@ -132,10 +132,11 @@ else if (swaggerUiConfigParameters.isValidUrl(swaggerUiUrl)) else swaggerUiConfigParameters.addUrl(apiDocsUrl); if (!CollectionUtils.isEmpty(swaggerUiConfig.getUrls())) { - swaggerUiConfigParameters.setUrls(swaggerUiConfig.cloneUrls()); - swaggerUiConfigParameters.getUrls().forEach(swaggerUrl -> { + swaggerUiConfig.cloneUrls().forEach(swaggerUrl -> { + swaggerUiConfigParameters.getUrls().remove(swaggerUrl); if (!swaggerUiConfigParameters.isValidUrl(swaggerUrl.getUrl())) swaggerUrl.setUrl(buildUrlWithContextPath(swaggerUrl.getUrl())); + swaggerUiConfigParameters.getUrls().add(swaggerUrl); }); } } diff --git a/springdoc-openapi-common/src/main/resources/META-INF/spring.factories b/springdoc-openapi-common/src/main/resources/META-INF/spring.factories index e38ba1928..942017b70 100644 --- a/springdoc-openapi-common/src/main/resources/META-INF/spring.factories +++ b/springdoc-openapi-common/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springdoc.core.SpringDocConfiguration,\ +org.springdoc.core.SpringDocSpecPropertiesConfiguration,\ org.springdoc.core.SpringDocConfigProperties \ No newline at end of file diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java b/springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java new file mode 100644 index 000000000..ac0cc5175 --- /dev/null +++ b/springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerIndexTransformerTest.java @@ -0,0 +1,69 @@ +package org.springdoc.ui; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springdoc.core.SwaggerUiConfigParameters; +import org.springdoc.core.SwaggerUiConfigProperties; +import org.springdoc.core.SwaggerUiOAuthProperties; +import org.springdoc.core.providers.ObjectMapperProvider; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +@ExtendWith(MockitoExtension.class) +public class AbstractSwaggerIndexTransformerTest { + + private SwaggerUiConfigProperties swaggerUiConfig; + @Mock + private SwaggerUiOAuthProperties swaggerUiOAuthProperties; + @Mock + private SwaggerUiConfigParameters swaggerUiConfigParameters; + @Mock + private ObjectMapperProvider objectMapperProvider; + + private AbstractSwaggerIndexTransformer underTest; + + private final String swaggerInitJs = "window.onload = function() {\n" + + "\n" + + " window.ui = SwaggerUIBundle({\n" + + " url: \"https://petstore.swagger.io/v2/swagger.json\",\n" + + " dom_id: '#swagger-ui',\n" + + " deepLinking: true,\n" + + " presets: [\n" + + " SwaggerUIBundle.presets.apis,\n" + + " SwaggerUIStandalonePreset\n" + + " ],\n" + + " plugins: [\n" + + " SwaggerUIBundle.plugins.DownloadUrl\n" + + " ],\n" + + " layout: \"StandaloneLayout\"\n" + + " });\n" + + "\n" + + " //\n" + + "};"; + private final InputStream is = new ByteArrayInputStream(swaggerInitJs.getBytes(StandardCharsets.UTF_8)); + + private final String apiDocUrl = "http://test.springdoc.com/apidoc"; + + @BeforeEach + public void setup(){ + swaggerUiConfig = new SwaggerUiConfigProperties(); + swaggerUiConfig.setUrl(apiDocUrl); + underTest = new AbstractSwaggerIndexTransformer(swaggerUiConfig, swaggerUiOAuthProperties, swaggerUiConfigParameters, objectMapperProvider); + + } + + @Test + void setApiDocUrlCorrectly() throws IOException { + String html = underTest.defaultTransformations(is); + assertThat(html, containsString(apiDocUrl)); + } +} diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerResourceResolverTest.java b/springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerResourceResolverTest.java new file mode 100644 index 000000000..a9af2e9f9 --- /dev/null +++ b/springdoc-openapi-common/src/test/java/org/springdoc/ui/AbstractSwaggerResourceResolverTest.java @@ -0,0 +1,51 @@ +package org.springdoc.ui; + +import java.io.File; +import java.util.Objects; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springdoc.core.SwaggerUiConfigProperties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AbstractSwaggerResourceResolverTest { + private SwaggerUiConfigProperties swaggerUiConfigProperties; + + private AbstractSwaggerResourceResolver abstractSwaggerResourceResolver; + + private final String VERSION = "4.18.2"; + + @BeforeEach + public void setup(){ + swaggerUiConfigProperties = new SwaggerUiConfigProperties(); + swaggerUiConfigProperties.setVersion(VERSION); + abstractSwaggerResourceResolver = new AbstractSwaggerResourceResolver(swaggerUiConfigProperties); + } + + @Test + void findWebJarResourcePath() { + String path = "swagger-ui/swagger-initializer.js"; + + String actual = abstractSwaggerResourceResolver.findWebJarResourcePath(path); + assertEquals("swagger-ui" + File.separator + "4.18.2" + File.separator + "swagger-initializer.js", actual); + } + + @Test + void returNullWhenPathIsSameAsWebjar() { + String path = "swagger-ui"; + + String actual = abstractSwaggerResourceResolver.findWebJarResourcePath(path); + assertTrue(Objects.isNull(actual)); + } + + @Test + void returNullWhenVersionIsNull() { + String path = "swagger-ui/swagger-initializer.js"; + swaggerUiConfigProperties.setVersion(null); + + String actual = abstractSwaggerResourceResolver.findWebJarResourcePath(path); + assertTrue(Objects.isNull(actual)); + } +} diff --git a/springdoc-openapi-data-rest/pom.xml b/springdoc-openapi-data-rest/pom.xml index 3b0e2c735..4dc54e5f3 100644 --- a/springdoc-openapi-data-rest/pom.xml +++ b/springdoc-openapi-data-rest/pom.xml @@ -4,7 +4,7 @@ springdoc-openapi org.springdoc - 1.7.0 + 1.8.0 springdoc-openapi-data-rest diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java index 3385d5327..b1d891ba7 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringDocDataRestConfiguration.java @@ -242,16 +242,18 @@ class QuerydslProvider { /** * Query dsl querydsl predicate operation customizer querydsl predicate operation customizer. * - * @param querydslBindingsFactory the querydsl bindings factory + * @param querydslBindingsFactory the querydsl bindings factory + * @param springDocConfigProperties the spring doc config properties * @return the querydsl predicate operation customizer */ @Bean @ConditionalOnMissingBean @Lazy(false) - QuerydslPredicateOperationCustomizer queryDslQuerydslPredicateOperationCustomizer(Optional querydslBindingsFactory) { + QuerydslPredicateOperationCustomizer queryDslQuerydslPredicateOperationCustomizer(Optional querydslBindingsFactory, + SpringDocConfigProperties springDocConfigProperties) { if (querydslBindingsFactory.isPresent()) { getConfig().addRequestWrapperToIgnore(Predicate.class); - return new QuerydslPredicateOperationCustomizer(querydslBindingsFactory.get()); + return new QuerydslPredicateOperationCustomizer(querydslBindingsFactory.get(), springDocConfigProperties); } return null; } diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java index fe850506c..aba66cf5d 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/SpringRepositoryRestResourceProvider.java @@ -223,7 +223,7 @@ public List getRouterOperations(OpenAPI openAPI, Locale locale) boolean hiddenRepository = (AnnotationUtils.findAnnotation(repository, Hidden.class) != null); if (!hiddenRepository) { - if (resourceMetadata.isExported()) { + if (resourceMetadata!=null && resourceMetadata.isExported()) { for (HandlerMapping handlerMapping : handlerMappingList) { if (handlerMapping instanceof RepositoryRestHandlerMapping) { RepositoryRestHandlerMapping repositoryRestHandlerMapping = (RepositoryRestHandlerMapping) handlerMapping; diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestOperationService.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestOperationService.java index 83f858ca9..f058e8c53 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestOperationService.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestOperationService.java @@ -207,7 +207,7 @@ private Operation buildSearchOperation(HandlerMethod handlerMethod, DataRestRepo ResourceDescription description = parameterMetadatum.getDescription(); if (description instanceof TypedResourceDescription) { Type type = getParameterType(pName, method, description); - Schema schema = SpringDocAnnotationsUtils.extractSchema(openAPI.getComponents(), type, null, null); + Schema schema = SpringDocAnnotationsUtils.extractSchema(openAPI.getComponents(), type, null, null, openAPI.getSpecVersion()); Parameter parameter = getParameterFromAnnotations(openAPI, methodAttributes, method, pName); if (parameter == null) { parameter = new Parameter().name(pName).in(ParameterIn.QUERY.toString()).schema(schema); diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java index 4a8b69ff5..24367c782 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/core/DataRestRequestService.java @@ -159,7 +159,7 @@ public void buildCommonParameters(OpenAPI openAPI, RequestMethod requestMethod, ParameterInfo parameterInfo = new ParameterInfo(pName, methodParameter, parameterBuilder, parameterDoc); if (isParamToIgnore(methodParameter)) { if (PersistentEntityResource.class.equals(methodParameter.getParameterType())) { - Schema schema = SpringDocAnnotationsUtils.resolveSchemaFromType(domainType, openAPI.getComponents(), null, methodParameter.getParameterAnnotations()); + Schema schema = SpringDocAnnotationsUtils.resolveSchemaFromType(domainType, openAPI.getComponents(), null, methodParameter.getParameterAnnotations(), openAPI.getSpecVersion()); parameterInfo.setParameterModel(new Parameter().schema(schema)); } else if (methodParameter.getParameterAnnotation(BackendId.class) != null) { diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/customisers/QuerydslPredicateOperationCustomizer.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/customisers/QuerydslPredicateOperationCustomizer.java index 94b141599..f298f627a 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/customisers/QuerydslPredicateOperationCustomizer.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/customisers/QuerydslPredicateOperationCustomizer.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -45,6 +47,7 @@ import org.apache.commons.lang3.reflect.FieldUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springdoc.core.SpringDocConfigProperties; import org.springdoc.core.customizers.GlobalOperationCustomizer; import org.springframework.core.MethodParameter; @@ -75,13 +78,21 @@ public class QuerydslPredicateOperationCustomizer implements GlobalOperationCust */ private final QuerydslBindingsFactory querydslBindingsFactory; + /** + * The Spring doc config properties. + */ + private final SpringDocConfigProperties springDocConfigProperties; + /** * Instantiates a new Querydsl predicate operation customizer. * - * @param querydslBindingsFactory the querydsl bindings factory + * @param querydslBindingsFactory the querydsl bindings factory + * @param springDocConfigProperties the spring doc config properties */ - public QuerydslPredicateOperationCustomizer(QuerydslBindingsFactory querydslBindingsFactory) { + public QuerydslPredicateOperationCustomizer(QuerydslBindingsFactory querydslBindingsFactory, SpringDocConfigProperties springDocConfigProperties) { this.querydslBindingsFactory = querydslBindingsFactory; + this.springDocConfigProperties= springDocConfigProperties; + } @Override @@ -139,8 +150,8 @@ public Operation customize(Operation operation, HandlerMethod handlerMethod) { /** * Gets field value of boolean. * - * @param instance the instance - * @param fieldName the field name + * @param instance the instance + * @param fieldName the field name * @return the field value of boolean */ private boolean getFieldValueOfBoolean(QuerydslBindings instance, String fieldName) { @@ -158,7 +169,7 @@ private boolean getFieldValueOfBoolean(QuerydslBindings instance, String fieldNa /** * Extract qdsl bindings querydsl bindings. * - * @param predicate the predicate + * @param predicate the predicate * @return the querydsl bindings */ private QuerydslBindings extractQdslBindings(QuerydslPredicate predicate) { @@ -177,9 +188,9 @@ private QuerydslBindings extractQdslBindings(QuerydslPredicate predicate) { /** * Gets field values. * - * @param instance the instance - * @param fieldName the field name - * @param alternativeFieldName the alternative field name + * @param instance the instance + * @param fieldName the field name + * @param alternativeFieldName the alternative field name * @return the field values */ private Set getFieldValues(QuerydslBindings instance, String fieldName, String alternativeFieldName) { @@ -199,8 +210,8 @@ private Set getFieldValues(QuerydslBindings instance, String fieldName, /** * Gets path spec. * - * @param instance the instance - * @param fieldName the field name + * @param instance the instance + * @param fieldName the field name * @return the path spec */ private Map getPathSpec(QuerydslBindings instance, String fieldName) { @@ -217,7 +228,7 @@ private Map getPathSpec(QuerydslBindings instance, String fieldN /** * Gets path from path spec. * - * @param instance the instance + * @param instance the instance * @return the path from path spec */ private Optional> getPathFromPathSpec(Object instance) { @@ -236,8 +247,8 @@ private Optional> getPathFromPathSpec(Object instance) { /*** * Tries to figure out the Type of the field. It first checks the Qdsl pathSpecMap before checking the root class. Defaults to String.class - * @param fieldName The name of the field used as reference to get the type - * @param pathSpecMap The Qdsl path specifications as defined in the resolved bindings + * @param fieldName The name of the field used as reference to get the type + * @param pathSpecMap The Qdsl path specifications as defined in the resolved bindings * @param root The root type where the paths are gotten * @return The type of the field. Returns */ @@ -263,12 +274,12 @@ private Type getFieldType(String fieldName, Map pathSpecMap, Cla /*** * Constructs the parameter - * @param type The type of the parameter - * @param name The name of the parameter + * @param type The type of the parameter + * @param name The name of the parameter * @return The swagger parameter */ - private io.swagger.v3.oas.models.parameters.Parameter buildParam(Type type, String name) { - io.swagger.v3.oas.models.parameters.Parameter parameter = new io.swagger.v3.oas.models.parameters.Parameter(); + private Parameter buildParam(Type type, String name) { + Parameter parameter = new Parameter(); if (StringUtils.isBlank(parameter.getName())) { parameter.setName(name); @@ -285,7 +296,7 @@ private io.swagger.v3.oas.models.parameters.Parameter buildParam(Type type, Stri schema = primitiveType.createProperty(); } else { - ResolvedSchema resolvedSchema = ModelConverters.getInstance() + ResolvedSchema resolvedSchema = ModelConverters.getInstance(springDocConfigProperties.isOpenapi31()) .resolveAsResolvedSchema( new io.swagger.v3.core.converter.AnnotatedType(type).resolveAsRef(true)); // could not resolve the schema or this schema references other schema diff --git a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/utils/SpringDocDataRestUtils.java b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/utils/SpringDocDataRestUtils.java index 0e6bd8063..191e9d903 100644 --- a/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/utils/SpringDocDataRestUtils.java +++ b/springdoc-openapi-data-rest/src/main/java/org/springdoc/data/rest/utils/SpringDocDataRestUtils.java @@ -2,19 +2,21 @@ * * * * * * - * * * * Copyright 2019-2022 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 + * * * * * Copyright 2019-2022 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. * * * * - * * * * 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. * * * * * * @@ -37,11 +39,11 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.media.MediaType; -import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.RequestBody; @@ -53,9 +55,11 @@ import org.springframework.data.mapping.SimpleAssociationHandler; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.mapping.ResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.webmvc.RestMediaTypes; +import org.springframework.data.util.TypeInformation; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.util.CollectionUtils; @@ -115,30 +119,30 @@ public SpringDocDataRestUtils(LinkRelationProvider linkRelationProvider, Reposit */ public void customise(OpenAPI openAPI, ResourceMappings mappings, PersistentEntities persistentEntities) { - persistentEntities.getManagedTypes().stream().forEach(typeInformation -> - { - Class domainType = typeInformation.getType(); - ResourceMetadata resourceMetadata = mappings.getMetadataFor(domainType); - final PersistentEntity entity = persistentEntities.getRequiredPersistentEntity(domainType); - EntityInfo entityInfo = new EntityInfo(); - entityInfo.setDomainType(domainType); - List ignoredFields = getIgnoredFields(resourceMetadata, entity); - if (!repositoryRestConfiguration.isIdExposedFor(entity.getType())) - entityInfo.setIgnoredFields(ignoredFields); - List associationsFields = getAssociationsFields(resourceMetadata, entity); - entityInfo.setAssociationsFields(associationsFields); - entityInoMap.put(domainType.getSimpleName(), entityInfo); - } - ); + for (PersistentEntity> persistentEntity : persistentEntities) { + TypeInformation typeInformation = persistentEntity.getTypeInformation(); + Class domainType = typeInformation.getType(); + ResourceMetadata resourceMetadata = mappings.getMetadataFor(domainType); + final PersistentEntity entity = persistentEntities.getRequiredPersistentEntity(domainType); + EntityInfo entityInfo = new EntityInfo(); + entityInfo.setDomainType(domainType); + List ignoredFields = getIgnoredFields(resourceMetadata, entity); + if (!repositoryRestConfiguration.isIdExposedFor(entity.getType())) + entityInfo.setIgnoredFields(ignoredFields); + List associationsFields = getAssociationsFields(resourceMetadata, entity); + entityInfo.setAssociationsFields(associationsFields); + entityInoMap.put(domainType.getSimpleName(), entityInfo); + } openAPI.getPaths().entrySet().stream() .forEach(stringPathItemEntry -> { PathItem pathItem = stringPathItemEntry.getValue(); pathItem.readOperations().forEach(operation -> { + boolean openapi31 = SpecVersion.V31 == openAPI.getSpecVersion(); RequestBody requestBody = operation.getRequestBody(); - updateRequestBody(openAPI, requestBody); + updateRequestBody(openAPI, requestBody, openapi31); ApiResponses apiResponses = operation.getResponses(); - apiResponses.forEach((code, apiResponse) -> updateApiResponse(openAPI, openAPI.getComponents(), apiResponse)); + apiResponses.forEach((code, apiResponse) -> updateApiResponse(openAPI, openAPI.getComponents(), apiResponse, openapi31)); }); }); @@ -147,11 +151,12 @@ public void customise(OpenAPI openAPI, ResourceMappings mappings, PersistentEnti /** * Update api response. * - * @param openAPI the open api - * @param components the components + * @param openAPI the open api + * @param components the components * @param apiResponse the api response + * @param openapi31 the openapi 31 */ - private void updateApiResponse(OpenAPI openAPI, Components components, ApiResponse apiResponse) { + private void updateApiResponse(OpenAPI openAPI, Components components, ApiResponse apiResponse, boolean openapi31) { if (apiResponse != null && !CollectionUtils.isEmpty(apiResponse.getContent())) { apiResponse.getContent().values().forEach(mediaType -> { Schema schema = mediaType.getSchema(); @@ -160,7 +165,7 @@ private void updateApiResponse(OpenAPI openAPI, Components components, ApiRespon Set entitiesNames = entityInoMap.keySet(); entitiesNames.forEach(entityName -> { if (key.endsWith(entityName)) - updateResponseSchema(entityName, components.getSchemas().get(key), openAPI.getComponents()); + updateResponseSchema(entityName, components.getSchemas().get(key), openAPI.getComponents(), openapi31); }); } }); @@ -170,20 +175,21 @@ private void updateApiResponse(OpenAPI openAPI, Components components, ApiRespon /** * Update request body. * - * @param openAPI the open api + * @param openAPI the open api * @param requestBody the request body + * @param openapi31 the openapi 31 */ - private void updateRequestBody(OpenAPI openAPI, RequestBody requestBody) { + private void updateRequestBody(OpenAPI openAPI, RequestBody requestBody, boolean openapi31) { if (requestBody != null && !CollectionUtils.isEmpty(requestBody.getContent())) { requestBody.getContent().values().forEach(mediaType -> { Schema schema = mediaType.getSchema(); if (schema.get$ref() != null && !schema.get$ref().endsWith(REQUEST_BODY)) { String key = schema.get$ref().substring(21); if (entityInoMap.containsKey(key)) - updateRequestBodySchema(key, schema, openAPI.getComponents()); + updateRequestBodySchema(key, schema, openAPI.getComponents(), openapi31); } else if (schema instanceof ComposedSchema) { - updateComposedSchema((ComposedSchema) schema, REQUEST_BODY, openAPI.getComponents()); + updateComposedSchema((ComposedSchema) schema, REQUEST_BODY, openAPI.getComponents(), openapi31); } }); } @@ -192,18 +198,19 @@ else if (schema instanceof ComposedSchema) { /** * Gets request body schema. * - * @param className the class name - * @param schema the schema + * @param className the class name + * @param schema the schema * @param components the components + * @param openapi31 the openapi 31 * @return the request body schema */ - private void updateRequestBodySchema(String className, Schema schema, Components components) { + private void updateRequestBodySchema(String className, Schema schema, Components components, boolean openapi31) { //update ref String newKey = className + REQUEST_BODY; schema.set$ref(newKey); //create new schema Class schemaImplementation = entityInoMap.get(className).getDomainType(); - ResolvedSchema resolvedSchema = ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation)); + ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31).readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation)); Map schemaMap; if (resolvedSchema != null) { schemaMap = resolvedSchema.referencedSchemas; @@ -214,19 +221,12 @@ private void updateRequestBodySchema(String className, Schema schema, Components Map properties = referencedSchema.getProperties(); updateRequestBodySchemaProperties(key, referencedSchema, properties); if (referencedSchema instanceof ComposedSchema) - updateComposedSchema((ComposedSchema) referencedSchema, REQUEST_BODY, components); + updateComposedSchema((ComposedSchema) referencedSchema, REQUEST_BODY, components, openapi31); }); } } } - /** - * Update request body schema properties. - * - * @param key the key - * @param referencedSchema the referenced schema - * @param properties the properties - */ private void updateRequestBodySchemaProperties(String key, Schema referencedSchema, Map properties) { if (!CollectionUtils.isEmpty(referencedSchema.getProperties())) { Iterator> it = properties.entrySet().iterator(); @@ -246,12 +246,13 @@ private void updateRequestBodySchemaProperties(String key, Schema referencedSche /** * Update response schema schema. * - * @param className the class name + * @param className the class name * @param existingSchema the existing schema - * @param components the components + * @param components the components + * @param openapi31 the openapi 31 * @return the schema */ - private Schema updateResponseSchema(String className, Schema existingSchema, Components components) { + private Schema updateResponseSchema(String className, Schema existingSchema, Components components, boolean openapi31) { Map properties = existingSchema.getProperties(); EntityInfo entityInfo = entityInoMap.get(className); if (!CollectionUtils.isEmpty(properties)) { @@ -262,7 +263,7 @@ private Schema updateResponseSchema(String className, Schema existingSchema, Com if (entityInfo.getIgnoredFields().contains(propId) || entityInfo.getAssociationsFields().contains(propId)) it.remove(); else if (EMBEDDED.equals(propId)) { - updateResponseSchemaEmbedded(components, entityInfo, entry); + updateResponseSchemaEmbedded(components, entityInfo, entry, openapi31); } } } @@ -274,11 +275,12 @@ else if (EMBEDDED.equals(propId)) { * * @param components the components * @param entityInfo the entity info - * @param entry the entry + * @param entry the entry + * @param openapi31 the openapi 31 */ - private void updateResponseSchemaEmbedded(Components components, EntityInfo entityInfo, Entry entry) { + private void updateResponseSchemaEmbedded(Components components, EntityInfo entityInfo, Entry entry, boolean openapi31) { String entityClassName = linkRelationProvider.getCollectionResourceRelFor(entityInfo.getDomainType()).value(); - ArraySchema arraySchema = (ArraySchema) ((ObjectSchema) entry.getValue()).getProperties().get(entityClassName); + ArraySchema arraySchema = (ArraySchema) entry.getValue().getProperties().get(entityClassName); if (arraySchema != null) { Schema itemsSchema = arraySchema.getItems(); Set entitiesNames = entityInoMap.keySet(); @@ -286,13 +288,13 @@ private void updateResponseSchemaEmbedded(Components components, EntityInfo enti String key = itemsSchema.get$ref().substring(21); if (entitiesNames.contains(key)) { String newKey = itemsSchema.get$ref() + RESPONSE; - createNewResponseSchema(key, components); + createNewResponseSchema(key, components, openapi31); itemsSchema.set$ref(newKey); - updateResponseSchema(key, components.getSchemas().get(key + RESPONSE), components); + updateResponseSchema(key, components.getSchemas().get(key + RESPONSE), components, openapi31); } } else if (itemsSchema instanceof ComposedSchema) { - updateComposedSchema((ComposedSchema) itemsSchema, RESPONSE, components); + updateComposedSchema((ComposedSchema) itemsSchema, RESPONSE, components, openapi31); } } } @@ -300,20 +302,21 @@ else if (itemsSchema instanceof ComposedSchema) { /** * Create new response schema schema. * - * @param className the class name + * @param className the class name * @param components the components + * @param openapi31 the openapi 31 * @return the schema */ - private Schema createNewResponseSchema(String className, Components components) { + private Schema createNewResponseSchema(String className, Components components, boolean openapi31) { Class schemaImplementation = entityInoMap.get(className).getDomainType(); Schema schemaObject = new Schema(); - ResolvedSchema resolvedSchema = ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation)); + ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31).readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation)); Map schemaMap; if (resolvedSchema != null) { schemaMap = resolvedSchema.referencedSchemas; if (schemaMap != null) { schemaMap.forEach((key, referencedSchema) -> - addSchemas(components, key, referencedSchema)); + addSchemas(components, key, referencedSchema, openapi31)); } schemaObject = resolvedSchema.schema; } @@ -324,11 +327,12 @@ private Schema createNewResponseSchema(String className, Components components) /** * Add schemas. * - * @param components the components - * @param key the key + * @param components the components + * @param key the key * @param referencedSchema the referenced schema + * @param openapi31 the openapi 31 */ - private void addSchemas(Components components, String key, Schema referencedSchema) { + private void addSchemas(Components components, String key, Schema referencedSchema, boolean openapi31) { Map properties = referencedSchema.getProperties(); if (!CollectionUtils.isEmpty(referencedSchema.getProperties())) { Iterator> it = properties.entrySet().iterator(); @@ -341,7 +345,7 @@ private void addSchemas(Components components, String key, Schema referencedSche } } if (referencedSchema instanceof ComposedSchema) { - updateComposedSchema((ComposedSchema) referencedSchema, RESPONSE, null); + updateComposedSchema((ComposedSchema) referencedSchema, RESPONSE, null, openapi31); } components.addSchemas(key + RESPONSE, referencedSchema); } @@ -350,32 +354,34 @@ private void addSchemas(Components components, String key, Schema referencedSche * Update composed schema. * * @param referencedSchema the referenced schema - * @param suffix the suffix - * @param components the components + * @param suffix the suffix + * @param components the components + * @param openapi31 the openapi 31 */ - private void updateComposedSchema(ComposedSchema referencedSchema, String suffix, Components components) { + private void updateComposedSchema(ComposedSchema referencedSchema, String suffix, Components components, boolean openapi31) { //Update the allOf ComposedSchema composedSchema = referencedSchema; List allOfSchemas = composedSchema.getAllOf(); - updateKey(allOfSchemas, suffix, components); + updateKey(allOfSchemas, suffix, components, openapi31); //Update the oneOf List oneOfSchemas = composedSchema.getOneOf(); - updateKey(oneOfSchemas, suffix, components); + updateKey(oneOfSchemas, suffix, components, openapi31); } /** * Update key. * * @param allSchemas the all schemas - * @param suffix can be Resposne or RequestBody + * @param suffix can be Resposne or RequestBody * @param components the components + * @param openapi31 the openapi 31 */ - private void updateKey(List allSchemas, String suffix, Components components) { + private void updateKey(List allSchemas, String suffix, Components components, boolean openapi31) { if (!CollectionUtils.isEmpty(allSchemas)) for (Schema allSchema : allSchemas) { if (allSchema.get$ref() != null) { String allKey = allSchema.get$ref().substring(21); - updateSingleKey(suffix, components, allSchema, allKey); + updateSingleKey(suffix, components, allSchema, allKey, openapi31); } } } @@ -383,17 +389,18 @@ private void updateKey(List allSchemas, String suffix, Components compon /** * Update single key. * - * @param suffix the suffix + * @param suffix the suffix * @param components the components - * @param allSchema the all schema - * @param allKey the all key + * @param allSchema the all schema + * @param allKey the all key + * @param openapi31 the openapi 31 */ - private void updateSingleKey(String suffix, Components components, Schema allSchema, String allKey) { + private void updateSingleKey(String suffix, Components components, Schema allSchema, String allKey, boolean openapi31) { if (!allKey.endsWith(REQUEST_BODY) && !allKey.endsWith(RESPONSE)) { String newAllKey = allKey + suffix; allSchema.set$ref(newAllKey); if (components != null && !components.getSchemas().containsKey(newAllKey) && entityInoMap.containsKey(allKey) && RESPONSE.equals(suffix)) { - createNewResponseSchema(allKey, components); + createNewResponseSchema(allKey, components, openapi31); } } } @@ -410,8 +417,11 @@ private List getAssociationsFields(ResourceMetadata List associationsFields = new ArrayList<>(); entity.doWithAssociations((SimpleAssociationHandler) association -> { PersistentProperty property = association.getInverse(); - String filedName = resourceMetadata.getMappingFor(property).getRel().value(); - associationsFields.add(filedName); + ResourceMapping mapping = resourceMetadata.getMappingFor(property); + if (mapping.isExported()) { + String filedName = mapping.getRel().value(); + associationsFields.add(filedName); + } }); return associationsFields; } @@ -431,8 +441,11 @@ private List getIgnoredFields(ResourceMetadata ignoredFields.add(idField); entity.doWithAssociations((SimpleAssociationHandler) association -> { PersistentProperty property = association.getInverse(); - String filedName = resourceMetadata.getMappingFor(property).getRel().value(); - ignoredFields.add(filedName); + ResourceMapping mapping = resourceMetadata.getMappingFor(property); + if (mapping.isExported()) { + String filedName = mapping.getRel().value(); + ignoredFields.add(filedName); + } }); } return ignoredFields; diff --git a/springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/app37/ProductRepository.java b/springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/app37/ProductRepository.java index 4dc7e562b..670024de5 100644 --- a/springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/app37/ProductRepository.java +++ b/springdoc-openapi-data-rest/src/test/java/test/org/springdoc/api/app37/ProductRepository.java @@ -4,6 +4,7 @@ * @author bnasslahsen */ +import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; @@ -31,7 +32,7 @@ List findByPrice( @Parameter( in = ParameterIn.QUERY, required = true ) - @Param("price")String price); + @Param("price") BigDecimal price); /** * 根据商品名称查询商品信息 diff --git a/springdoc-openapi-data-rest/src/test/resources/results/app24.json b/springdoc-openapi-data-rest/src/test/resources/results/app24.json index df767bc53..ad11e0fd4 100644 --- a/springdoc-openapi-data-rest/src/test/resources/results/app24.json +++ b/springdoc-openapi-data-rest/src/test/resources/results/app24.json @@ -90,10 +90,6 @@ "type": "integer", "format": "int64" }, - "numberOfElements": { - "type": "integer", - "format": "int32" - }, "size": { "type": "integer", "format": "int32" @@ -108,11 +104,11 @@ "type": "integer", "format": "int32" }, - "pageable": { - "$ref": "#/components/schemas/PageableObject" - }, "sort": { - "$ref": "#/components/schemas/SortObject" + "type": "array", + "items": { + "$ref": "#/components/schemas/SortObject" + } }, "first": { "type": "boolean" @@ -120,6 +116,13 @@ "last": { "type": "boolean" }, + "pageable": { + "$ref": "#/components/schemas/PageableObject" + }, + "numberOfElements": { + "type": "integer", + "format": "int32" + }, "empty": { "type": "boolean" } @@ -132,6 +135,18 @@ "type": "integer", "format": "int64" }, + "sort": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortObject" + } + }, + "paged": { + "type": "boolean" + }, + "unpaged": { + "type": "boolean" + }, "pageNumber": { "type": "integer", "format": "int32" @@ -139,28 +154,25 @@ "pageSize": { "type": "integer", "format": "int32" - }, - "sort": { - "$ref": "#/components/schemas/SortObject" - }, - "unpaged": { - "type": "boolean" - }, - "paged": { - "type": "boolean" } } }, "SortObject": { "type": "object", "properties": { - "sorted": { - "type": "boolean" + "direction": { + "type": "string" }, - "unsorted": { + "nullHandling": { + "type": "string" + }, + "ascending": { "type": "boolean" }, - "empty": { + "property": { + "type": "string" + }, + "ignoreCase": { "type": "boolean" } } diff --git a/springdoc-openapi-data-rest/src/test/resources/results/app27.json b/springdoc-openapi-data-rest/src/test/resources/results/app27.json index e1b282565..cb45a6931 100644 --- a/springdoc-openapi-data-rest/src/test/resources/results/app27.json +++ b/springdoc-openapi-data-rest/src/test/resources/results/app27.json @@ -58,6 +58,16 @@ "Pageable": { "type": "object", "properties": { + "offset": { + "type": "integer", + "format": "int64" + }, + "sort": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortObject" + } + }, "paged": { "type": "boolean" }, @@ -71,26 +81,25 @@ "pageSize": { "type": "integer", "format": "int32" - }, - "offset": { - "type": "integer", - "format": "int64" - }, - "sort": { - "$ref": "#/components/schemas/SortObject" } } }, "SortObject": { "type": "object", "properties": { - "sorted": { - "type": "boolean" + "direction": { + "type": "string" }, - "unsorted": { + "nullHandling": { + "type": "string" + }, + "ascending": { "type": "boolean" }, - "empty": { + "property": { + "type": "string" + }, + "ignoreCase": { "type": "boolean" } } diff --git a/springdoc-openapi-data-rest/src/test/resources/results/app37.json b/springdoc-openapi-data-rest/src/test/resources/results/app37.json index ee0f18b9c..f63bb69eb 100644 --- a/springdoc-openapi-data-rest/src/test/resources/results/app37.json +++ b/springdoc-openapi-data-rest/src/test/resources/results/app37.json @@ -339,7 +339,7 @@ "description": "test desc", "required": true, "schema": { - "type": "string" + "type": "number" } } ], diff --git a/springdoc-openapi-groovy/pom.xml b/springdoc-openapi-groovy/pom.xml index 8c6207f92..311e395fa 100644 --- a/springdoc-openapi-groovy/pom.xml +++ b/springdoc-openapi-groovy/pom.xml @@ -4,7 +4,7 @@ springdoc-openapi org.springdoc - 1.7.0 + 1.8.0 springdoc-openapi-groovy diff --git a/springdoc-openapi-groovy/src/test/groovy/test/org/springdoc/api/app1/CarController.groovy b/springdoc-openapi-groovy/src/test/groovy/test/org/springdoc/api/app1/CarController.groovy index e8ffbde5b..299cea645 100644 --- a/springdoc-openapi-groovy/src/test/groovy/test/org/springdoc/api/app1/CarController.groovy +++ b/springdoc-openapi-groovy/src/test/groovy/test/org/springdoc/api/app1/CarController.groovy @@ -20,6 +20,7 @@ package test.org.springdoc.api.app1 +import org.springdoc.api.annotations.ParameterObject import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -35,7 +36,7 @@ class CarController { } @GetMapping(path = '/cars') - List getCars() { + List getCars(@ParameterObject CarsFilter filter) { return carService.getCars() } @@ -43,4 +44,8 @@ class CarController { Car getCar(@PathVariable(value = 'carId') Long carId) { return carService.getCar(carId) } + + static class CarsFilter { + String name; + } } diff --git a/springdoc-openapi-groovy/src/test/resources/results/app1.json b/springdoc-openapi-groovy/src/test/resources/results/app1.json index ad2ff215c..2c5bad0bf 100644 --- a/springdoc-openapi-groovy/src/test/resources/results/app1.json +++ b/springdoc-openapi-groovy/src/test/resources/results/app1.json @@ -11,20 +11,19 @@ } ], "paths": { - "/cars/{carId}": { + "/cars": { "get": { "tags": [ "car-controller" ], - "operationId": "getCar", + "operationId": "getCars", "parameters": [ { - "name": "carId", - "in": "path", - "required": true, + "name": "name", + "in": "query", + "required": false, "schema": { - "type": "integer", - "format": "int64" + "type": "string" } } ], @@ -34,7 +33,10 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/Car" + "type": "array", + "items": { + "$ref": "#/components/schemas/Car" + } } } } @@ -42,22 +44,30 @@ } } }, - "/cars": { + "/cars/{carId}": { "get": { "tags": [ "car-controller" ], - "operationId": "getCars", + "operationId": "getCar", + "parameters": [ + { + "name": "carId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], "responses": { "200": { "description": "OK", "content": { "*/*": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Car" - } + "$ref": "#/components/schemas/Car" } } } diff --git a/springdoc-openapi-hateoas/pom.xml b/springdoc-openapi-hateoas/pom.xml index 0fba87f34..46c94a5e5 100644 --- a/springdoc-openapi-hateoas/pom.xml +++ b/springdoc-openapi-hateoas/pom.xml @@ -4,7 +4,7 @@ springdoc-openapi org.springdoc - 1.7.0 + 1.8.0 springdoc-openapi-hateoas diff --git a/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/SpringDocHateoasConfiguration.java b/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/SpringDocHateoasConfiguration.java index 0f78550de..b822605e0 100644 --- a/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/SpringDocHateoasConfiguration.java +++ b/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/SpringDocHateoasConfiguration.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; +import org.springdoc.core.Constants; import org.springdoc.core.SpringDocConfigProperties; import org.springdoc.core.SpringDocConfiguration; import org.springdoc.core.customizers.GlobalOpenApiCustomizer; @@ -95,11 +96,11 @@ CollectionModelContentConverter collectionModelContentConverter(HateoasHalProvid * @param halProvider the hal provider * @param springDocConfigProperties the spring doc config properties * @param objectMapperProvider the object mapper provider - * @return the open api customiser + * @return the open api customizer * @see org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider) org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider)org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer#serialize(Links, JsonGenerator, SerializerProvider) */ @Bean(LINKS_SCHEMA_CUSTOMISER) - @ConditionalOnMissingBean + @ConditionalOnMissingBean(name = Constants.LINKS_SCHEMA_CUSTOMISER) @Lazy(false) GlobalOpenApiCustomizer linksSchemaCustomiser(HateoasHalProvider halProvider, SpringDocConfigProperties springDocConfigProperties, ObjectMapperProvider objectMapperProvider) { if (!halProvider.isHalEnabled()) { diff --git a/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/converters/OpenApiHateoasLinksCustomiser.java b/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/converters/OpenApiHateoasLinksCustomiser.java index 289ac6254..638b93224 100644 --- a/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/converters/OpenApiHateoasLinksCustomiser.java +++ b/springdoc-openapi-hateoas/src/main/java/org/springdoc/hateoas/converters/OpenApiHateoasLinksCustomiser.java @@ -58,7 +58,7 @@ public OpenApiHateoasLinksCustomiser(SpringDocConfigProperties springDocConfigPr @Override public void customise(OpenAPI openApi) { - ResolvedSchema resolvedLinkSchema = ModelConverters.getInstance() + ResolvedSchema resolvedLinkSchema = ModelConverters.getInstance(springDocConfigProperties.isOpenapi31()) .resolveAsResolvedSchema(new AnnotatedType(Link.class)); openApi .schema("Link", resolvedLinkSchema.schema) diff --git a/springdoc-openapi-javadoc/pom.xml b/springdoc-openapi-javadoc/pom.xml index 812b03920..273029ec8 100644 --- a/springdoc-openapi-javadoc/pom.xml +++ b/springdoc-openapi-javadoc/pom.xml @@ -3,7 +3,7 @@ springdoc-openapi org.springdoc - 1.7.0 + 1.8.0 4.0.0 diff --git a/springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizer.java b/springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizer.java index a7f029e84..6daaf00b5 100644 --- a/springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizer.java +++ b/springdoc-openapi-javadoc/src/main/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizer.java @@ -22,21 +22,31 @@ package org.springdoc.openapi.javadoc; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; -import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JavaType; import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.core.converter.ModelConverterContext; +import io.swagger.v3.core.converter.ModelConverterContextImpl; import io.swagger.v3.core.util.AnnotationsUtils; import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springdoc.core.providers.JavadocProvider; import org.springdoc.core.providers.ObjectMapperProvider; @@ -48,6 +58,11 @@ */ public class JavadocPropertyCustomizer implements ModelConverter { + /** + * The constant LOGGER. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(JavadocPropertyCustomizer.class); + /** * The Javadoc provider. */ @@ -85,15 +100,32 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato Class cls = javaType.getRawClass(); Schema resolvedSchema = chain.next().resolve(type, context, chain); List fields = FieldUtils.getAllFieldsList(cls); - if (!CollectionUtils.isEmpty(fields)) { + List clsProperties = new ArrayList<>(); + try { + clsProperties = Arrays.asList(Introspector.getBeanInfo(cls).getPropertyDescriptors()); + } catch (IntrospectionException ignored) { + LOGGER.warn(ignored.getMessage()); + } + if (!CollectionUtils.isEmpty(fields) || !CollectionUtils.isEmpty(clsProperties)) { if (!type.isSchemaProperty()) { Schema existingSchema = context.resolve(type); - setJavadocDescription(cls, fields, existingSchema); + setJavadocDescription(cls, fields, clsProperties, existingSchema, false); } else if (resolvedSchema != null && resolvedSchema.get$ref() != null && resolvedSchema.get$ref().contains(AnnotationsUtils.COMPONENTS_REF)) { String schemaName = resolvedSchema.get$ref().substring(21); Schema existingSchema = context.getDefinedModels().get(schemaName); - setJavadocDescription(cls, fields, existingSchema); + setJavadocDescription(cls, fields, clsProperties, existingSchema, false); + } + else { + try { + Field processedTypesField = FieldUtils.getDeclaredField(ModelConverterContextImpl.class, "processedTypes", true); + Set processedType = (Set) processedTypesField.get(context); + if(processedType.contains(type)) + setJavadocDescription(cls, fields, clsProperties, resolvedSchema, true); + } + catch (IllegalAccessException e) { + LOGGER.warn(e.getMessage()); + } } } return resolvedSchema; @@ -105,18 +137,23 @@ else if (resolvedSchema != null && resolvedSchema.get$ref() != null && resolvedS /** * Sets javadoc description. * - * @param cls the cls - * @param fields the fields - * @param existingSchema the existing schema + * @param cls the cls + * @param fields the fields + * @param clsProperties the bean properties of cls + * @param existingSchema the existing schema + * @param isProcessedType the is processed type */ - void setJavadocDescription(Class cls, List fields, Schema existingSchema) { + public void setJavadocDescription(Class cls, List fields, List clsProperties, Schema existingSchema, boolean isProcessedType) { if (existingSchema != null) { - if (StringUtils.isBlank(existingSchema.getDescription())) { - existingSchema.setDescription(javadocProvider.getClassJavadoc(cls)); + if (StringUtils.isBlank(existingSchema.getDescription()) && !isProcessedType) { + String classJavadoc = javadocProvider.getClassJavadoc(cls); + if (StringUtils.isNotBlank(classJavadoc)) { + existingSchema.setDescription(classJavadoc); + } } Map properties = existingSchema.getProperties(); if (!CollectionUtils.isEmpty(properties)) { - if (cls.getSuperclass() != null && "java.lang.Record".equals(cls.getSuperclass().getName())) { + if (cls.getSuperclass() != null && "java.lang.Record".equals(cls.getSuperclass().getName())) { Map recordParamMap = javadocProvider.getRecordClassParamJavadoc(cls); properties.entrySet().stream() .filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription())) @@ -125,21 +162,53 @@ void setJavadocDescription(Class cls, List fields, Schema existingSche stringSchemaEntry.getValue().setDescription(recordParamMap.get(stringSchemaEntry.getKey())); }); } - properties.entrySet().stream() .filter(stringSchemaEntry -> StringUtils.isBlank(stringSchemaEntry.getValue().getDescription())) .forEach(stringSchemaEntry -> { - Optional optionalField = fields.stream().filter(field1 -> field1.getName().equals(stringSchemaEntry.getKey())).findAny(); + Optional optionalField = fields.stream().filter(field1 -> findFields(stringSchemaEntry, field1)).findAny(); optionalField.ifPresent(field -> { String fieldJavadoc = javadocProvider.getFieldJavadoc(field); if (StringUtils.isNotBlank(fieldJavadoc)) stringSchemaEntry.getValue().setDescription(fieldJavadoc); }); + if (StringUtils.isBlank(stringSchemaEntry.getValue().getDescription())) { + Optional optionalPd = clsProperties.stream().filter(pd -> pd.getName().equals(stringSchemaEntry.getKey())).findAny(); + optionalPd.ifPresent(pd1 -> { + if(pd1.getReadMethod() != null) { + String fieldJavadoc = javadocProvider.getMethodJavadocDescription(pd1.getReadMethod()); + if (StringUtils.isNotBlank(fieldJavadoc)) + stringSchemaEntry.getValue().setDescription(fieldJavadoc); + } + }); + } }); } - fields.stream().filter(f -> f.isAnnotationPresent(JsonUnwrapped.class)) - .forEach(f -> setJavadocDescription(f.getType(), FieldUtils.getAllFieldsList(f.getType()), existingSchema)); + } + } + /** + * Find fields boolean. + * + * @param stringSchemaEntry the string schema entry + * @param field the field + * @return the boolean + */ + private boolean findFields(Entry stringSchemaEntry, Field field) { + if (field.getName().equals(stringSchemaEntry.getKey())) { + return true; + } + else { + JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class); + if (jsonPropertyAnnotation != null) { + String jsonPropertyName = jsonPropertyAnnotation.value(); + if (jsonPropertyName.equals(stringSchemaEntry.getKey())) { + return true; + } + } + else if (field.getName().equalsIgnoreCase(stringSchemaEntry.getKey().replaceAll("_", ""))) { + return true; + } + return false; } } } diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController.java index f438acd0a..4d6d4d612 100644 --- a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController.java +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController.java @@ -10,6 +10,7 @@ import javax.validation.constraints.Size; import org.springframework.validation.annotation.Validated; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,7 +35,7 @@ public class PersonController { * @return the person */ @RequestMapping(path = "/person", method = RequestMethod.POST) - public Person person(@Valid @RequestBody Person person) { + public Person person(@Valid @RequestBody Person person) throws HttpMediaTypeNotSupportedException { int nxt = ran.nextInt(10); if (nxt >= 5) { @@ -52,7 +53,7 @@ public Person person(@Valid @RequestBody Person person) { @RequestMapping(path = "/personByLastName", method = RequestMethod.GET) public List findByLastName(@RequestParam(name = "lastName", required = true) @NotNull @NotBlank - @Size(max = 10) String lastName) { + @Size(max = 10) String lastName) throws HttpMediaTypeNotSupportedException { List hardCoded = new ArrayList<>(); Person person = new Person(); person.setAge(20); diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController2.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController2.java index 7e16c4bdb..8154a4719 100644 --- a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController2.java +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app110/PersonController2.java @@ -10,6 +10,7 @@ import javax.validation.constraints.Size; import org.springframework.validation.annotation.Validated; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,7 +35,7 @@ public class PersonController2 { * @return the person */ @RequestMapping(path = "/person2", method = RequestMethod.POST) - public Person person(@Valid @RequestBody Person person) { + public Person person(@Valid @RequestBody Person person) throws HttpMediaTypeNotSupportedException { int nxt = ran.nextInt(10); if (nxt >= 5) { @@ -52,7 +53,7 @@ public Person person(@Valid @RequestBody Person person) { @RequestMapping(path = "/personByLastName2", method = RequestMethod.GET) public List findByLastName(@RequestParam(name = "lastName", required = true) @NotNull @NotBlank - @Size(max = 10) String lastName) { + @Size(max = 10) String lastName) throws HttpMediaTypeNotSupportedException { List hardCoded = new ArrayList<>(); Person person = new Person(); person.setAge(20); diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app112/sample/PersonController2.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app112/sample/PersonController2.java index f6852430f..75aa3f775 100644 --- a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app112/sample/PersonController2.java +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app112/sample/PersonController2.java @@ -12,6 +12,7 @@ import test.org.springdoc.api.app112.Person; import org.springframework.validation.annotation.Validated; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -36,7 +37,7 @@ public class PersonController2 { * @return the person */ @RequestMapping(path = "/person2", method = RequestMethod.POST) - public Person person(@Valid @RequestBody Person person) { + public Person person(@Valid @RequestBody Person person) throws HttpMediaTypeNotSupportedException { int nxt = ran.nextInt(10); if (nxt >= 5) { @@ -54,7 +55,7 @@ public Person person(@Valid @RequestBody Person person) { @RequestMapping(path = "/personByLastName2", method = RequestMethod.GET) public List findByLastName(@RequestParam(name = "lastName", required = true) @NotNull @NotBlank - @Size(max = 10) String lastName) { + @Size(max = 10) String lastName) throws HttpMediaTypeNotSupportedException { List hardCoded = new ArrayList<>(); Person person = new Person(); person.setAge(20); diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app13/PersonDTO.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app13/PersonDTO.java index 13ec5a6d4..b61220a66 100644 --- a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app13/PersonDTO.java +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app13/PersonDTO.java @@ -18,9 +18,13 @@ package test.org.springdoc.api.app13; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + /** * The type Person dto. */ +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class PersonDTO { /** * The Email. diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Animal.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Animal.java new file mode 100644 index 000000000..b3494ed3b --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Animal.java @@ -0,0 +1,17 @@ +package test.org.springdoc.api.app170; + + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Interface of the Animal. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) +@JsonSubTypes({ + @JsonSubTypes.Type(value = Dog.class, name = "dog"), + @JsonSubTypes.Type(value = Cat.class, name = "cat"), +}) +@Schema(description = "Represents an Animal class.") +public interface Animal {} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/BasicController.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/BasicController.java new file mode 100644 index 000000000..776fc465a --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/BasicController.java @@ -0,0 +1,19 @@ +package test.org.springdoc.api.app170; + +import io.swagger.v3.oas.annotations.Operation; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(path = "/") +public class BasicController { + + @GetMapping("/test1") + @Operation(summary = "get1", description = "Provides an animal.") + public Animal get1() { + + return new Dog("Foo", 12); + } +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Cat.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Cat.java new file mode 100644 index 000000000..7f382e927 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Cat.java @@ -0,0 +1,22 @@ +package test.org.springdoc.api.app170; + + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema +public class Cat implements Animal { + + private Integer speed; + + public Cat(Integer speed) { + this.speed = speed; + } + + public Integer getSpeed() { + return speed; + } + + public void setSpeed(Integer speed) { + this.speed = speed; + } +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Dog.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Dog.java new file mode 100644 index 000000000..6d365a191 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/Dog.java @@ -0,0 +1,33 @@ +package test.org.springdoc.api.app170; + + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "Represents a Dog class.") +public class Dog implements Animal { + + private String name; + + private Integer age; + + public Dog(String name, Integer age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/SpringDocApp170Test.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/SpringDocApp170Test.java new file mode 100644 index 000000000..b703c9638 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app170/SpringDocApp170Test.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2019-2023 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.app170; + +import test.org.springdoc.api.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * The type Spring doc app 193 test. + */ +public class SpringDocApp170Test extends AbstractSpringDocTest { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + } + +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/HelloController.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/HelloController.java new file mode 100644 index 000000000..cabd889b5 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/HelloController.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2019-2023 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.app171; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * The type Hello controller. + */ +@RestController +public class HelloController { + + /** + * PersonProjection interface. + * + * @return the PersonProjection + */ + @GetMapping(value = "/persons") + public PersonProjection persons() { + return new PersonDTO(); + } + +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonDTO.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonDTO.java new file mode 100644 index 000000000..010b6a328 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonDTO.java @@ -0,0 +1,73 @@ +/* + * + * * Copyright 2019-2023 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.app171; + +/** + * Simulate a dynamically generated class that implements the PersonProjection interface. + */ +public class PersonDTO implements PersonProjection { + private String email; + + private String firstName; + + private String lastName; + + /** + * Instantiates a new Person dto. + */ + public PersonDTO() { + } + + /** + * Instantiates a new Person dto. + * + * @param email the email + * @param firstName the first name + * @param lastName the last name + */ + public PersonDTO(final String email, final String firstName, final String lastName) { + this.email = email; + this.firstName = firstName; + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(final String email) { + this.email = email; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(final String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(final String lastName) { + this.lastName = lastName; + } +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonProjection.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonProjection.java new file mode 100644 index 000000000..a750b8573 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/PersonProjection.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2019-2023 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.app171; + +/** + * The type PersonProjection dto interface. + */ +public interface PersonProjection { + /** + * The Email. + * + */ + String getEmail(); + + /** + * The First name. + * + */ + String getFirstName(); + + /** + * The Last name. + * + */ + String getLastName(); +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java new file mode 100644 index 000000000..bf70d207c --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2019-2023 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.app171; + +import test.org.springdoc.api.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * The type Spring doc app 193 test. + */ +public class SpringDocApp171Test extends AbstractSpringDocTest { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + } + +} diff --git a/springdoc-openapi-javadoc/src/test/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizerTest.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app172/JavadocPropertyCustomizerTest.java similarity index 88% rename from springdoc-openapi-javadoc/src/test/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizerTest.java rename to springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app172/JavadocPropertyCustomizerTest.java index 83c86eb55..0827cf923 100644 --- a/springdoc-openapi-javadoc/src/test/java/org/springdoc/openapi/javadoc/JavadocPropertyCustomizerTest.java +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app172/JavadocPropertyCustomizerTest.java @@ -20,8 +20,11 @@ * */ -package org.springdoc.openapi.javadoc; +package test.org.springdoc.api.app172; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -49,6 +52,8 @@ import org.mockito.MockitoAnnotations; import org.springdoc.core.providers.JavadocProvider; import org.springdoc.core.providers.ObjectMapperProvider; +import org.springdoc.openapi.javadoc.JavadocPropertyCustomizer; +import org.springdoc.openapi.javadoc.SpringDocJavadocProvider; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -75,17 +80,17 @@ void setup() { /** - * Tests for {@link JavadocPropertyCustomizer#setJavadocDescription(Class, List, Schema)}. + * Tests for {@link JavadocPropertyCustomizer#setJavadocDescription(Class, List, List, Schema)}. */ @Nested class setJavadocDescription { @Test @EnabledForJreRange(min = JRE.JAVA_17) - void ifRecordObjectShouldGetField() throws IOException, ClassNotFoundException { + void ifRecordObjectShouldGetField() throws IOException, ClassNotFoundException, IntrospectionException { File recordObject = new File(tempDir, "RecordObject.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(recordObject))) { writer.println("/**"); - writer.println(" * Reord Object"); + writer.println(" * Record Object"); writer.println(" *"); writer.println(" * @param id the id"); writer.println(" * @param name the name"); @@ -122,7 +127,8 @@ void ifRecordObjectShouldGetField() throws IOException, ClassNotFoundException { .addProperty("id", new StringSchema().name("id")) .addProperty("name", new StringSchema().name("name")); - javadocPropertyCustomizer.setJavadocDescription(cls, fields, existingSchema); + List propertyDescriptors = Arrays.asList(Introspector.getBeanInfo(cls).getPropertyDescriptors()); + javadocPropertyCustomizer.setJavadocDescription(cls, fields, propertyDescriptors, existingSchema,false); assertEquals("Record Object", existingSchema.getDescription()); Map properties = existingSchema.getProperties(); diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/Example.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/Example.java new file mode 100644 index 000000000..b464b8493 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/Example.java @@ -0,0 +1,21 @@ +package test.org.springdoc.api.app173; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * The Example object + */ +@Schema +public class Example { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/ExampleController.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/ExampleController.java new file mode 100644 index 000000000..0fc537966 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/ExampleController.java @@ -0,0 +1,39 @@ +package test.org.springdoc.api.app173; + +import java.util.UUID; + +import io.swagger.v3.oas.annotations.Operation; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.http.HttpStatus.OK; + +/** + * The Example Controller + */ +@RestController +public class ExampleController { + + @PostMapping("/example") + @Operation(summary = "insert example", description = "Allows to insert an example") + public ResponseEntity postExample(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "${example.description}") @RequestBody Example example) { + return new ResponseEntity<>(UUID.randomUUID(), OK); + } + + @PutMapping("/example") + @Operation(summary = "update example", description = "Allows to update an example") + public ResponseEntity putExample(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "${example2.description:Default description for example}") @RequestBody Example example) { + return new ResponseEntity<>(UUID.randomUUID(), OK); + } + + @PatchMapping("/example") + @Operation(summary = "patch example", description = "Allows to patch an example") + public ResponseEntity patchExample(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Description without the use of variables") @RequestBody Example example) { + return new ResponseEntity<>(UUID.randomUUID(), OK); + } +} diff --git a/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java new file mode 100644 index 000000000..dc229e45d --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/java/test/org/springdoc/api/app173/SpringDocApp173Test.java @@ -0,0 +1,21 @@ +package test.org.springdoc.api.app173; + +import test.org.springdoc.api.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; + +/** + * The type Spring doc app 173 test. + */ +@TestPropertySource(properties = "example.description=The example object") +public class SpringDocApp173Test extends AbstractSpringDocTest { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + } + +} diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app104.json b/springdoc-openapi-javadoc/src/test/resources/results/app104.json index 686cad12f..010ae485e 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app104.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app104.json @@ -72,7 +72,8 @@ "components": { "schemas": { "Design": { - "type": "object" + "type": "object", + "description": "The type Design." } } } diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app105-3.json b/springdoc-openapi-javadoc/src/test/resources/results/app105-3.json index 782feff37..bccc4af1b 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app105-3.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app105-3.json @@ -546,10 +546,12 @@ "properties": { "id": { "type": "integer", + "description": "The Id.", "format": "int64" }, "name": { - "type": "string" + "type": "string", + "description": "The Name." } } }, @@ -623,10 +625,12 @@ "properties": { "id": { "type": "integer", + "description": "The Id.", "format": "int64" }, "name": { - "type": "string" + "type": "string", + "description": "The Name." } } } diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app114.json b/springdoc-openapi-javadoc/src/test/resources/results/app114.json index 51a4d927f..d7a3f0792 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app114.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app114.json @@ -77,4 +77,4 @@ } } } -} \ No newline at end of file +} diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app126.json b/springdoc-openapi-javadoc/src/test/resources/results/app126.json index 6827b7338..99adfdcd2 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app126.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app126.json @@ -56,27 +56,33 @@ "properties": { "instance": { "type": "string", - "format": "uri" + "format": "uri", + "description": "An absolute URI that identifies the specific occurrence of the problem.\n It may or may not yield further information if dereferenced." }, "type": { "type": "string", + "description": "An absolute URI that identifies the problem type. When dereferenced,\n it SHOULD provide human-readable documentation for the problem type\n (e.g., using HTML). When this member is not present, its value is\n assumed to be \"about:blank\".", "format": "uri" }, "parameters": { "type": "object", "additionalProperties": { "type": "object" - } + }, + "description": "Optional, additional attributes of the problem. Implementations can choose to ignore this in favor of concrete,\n typed fields." + }, + "title": { + "type": "string", + "description": "A short, human-readable summary of the problem type. It SHOULD NOT\n change from occurrence to occurrence of the problem, except for\n purposes of localisation." }, "status": { "type": "integer", + "description": "The HTTP status code generated by the origin server for this\n occurrence of the problem.", "format": "int32" }, - "title": { - "type": "string" - }, "detail": { - "type": "string" + "type": "string", + "description": "A human readable explanation specific to this occurrence of the problem." } }, "description": "The interface Problem." diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app13.json b/springdoc-openapi-javadoc/src/test/resources/results/app13.json index 700ae3248..ea027bb76 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app13.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app13.json @@ -60,11 +60,11 @@ "type": "string", "description": "The Email." }, - "firstName": { + "first_name": { "type": "string", "description": "The First name." }, - "lastName": { + "last_name": { "type": "string", "description": "The Last name." } @@ -73,4 +73,4 @@ } } } -} \ No newline at end of file +} diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app170.json b/springdoc-openapi-javadoc/src/test/resources/results/app170.json new file mode 100644 index 000000000..bd1ea9a79 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/resources/results/app170.json @@ -0,0 +1,90 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/test1": { + "get": { + "tags": [ + "basic-controller" + ], + "summary": "get1", + "description": "Provides an animal.", + "operationId": "get1", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Cat" + }, + { + "$ref": "#/components/schemas/Dog" + } + ] + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Animal": { + "type": "object", + "description": "Represents an Animal class." + }, + "Cat": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "type": "object", + "properties": { + "speed": { + "type": "integer", + "format": "int32" + } + } + } + ] + }, + "Dog": { + "type": "object", + "description": "Represents a Dog class.", + "allOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + } + } + } + ] + } + } + } +} diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app171.json b/springdoc-openapi-javadoc/src/test/resources/results/app171.json new file mode 100644 index 000000000..a64303bed --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/resources/results/app171.json @@ -0,0 +1,65 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "hello-controller", + "description": "The type Hello controller." + } + ], + "paths": { + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "summary": "PersonProjection interface.", + "description": "PersonProjection interface.", + "operationId": "persons", + "responses": { + "200": { + "description": "the PersonProjection", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/PersonProjection" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PersonProjection" : { + "type" : "object", + "properties" : { + "email" : { + "type" : "string", + "description" : "The Email." + }, + "firstName" : { + "type" : "string", + "description" : "The First name." + }, + "lastName" : { + "type" : "string", + "description" : "The Last name." + } + }, + "description" : "The type PersonProjection dto interface." + } + } + } +} \ No newline at end of file diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app173.json b/springdoc-openapi-javadoc/src/test/resources/results/app173.json new file mode 100644 index 000000000..d73d621c4 --- /dev/null +++ b/springdoc-openapi-javadoc/src/test/resources/results/app173.json @@ -0,0 +1,132 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "example-controller", + "description": "The Example Controller" + } + ], + "paths": { + "/example": { + "put": { + "tags": [ + "example-controller" + ], + "summary": "update example", + "description": "Allows to update an example", + "operationId": "putExample", + "requestBody": { + "description": "Default description for example", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "post": { + "tags": [ + "example-controller" + ], + "summary": "insert example", + "description": "Allows to insert an example", + "operationId": "postExample", + "requestBody": { + "description": "The example object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "patch": { + "tags": [ + "example-controller" + ], + "summary": "patch example", + "description": "Allows to patch an example", + "operationId": "patchExample", + "requestBody": { + "description": "Description without the use of variables", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Example" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Example": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "description": "The Example object" + } + } + } +} diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app2.json b/springdoc-openapi-javadoc/src/test/resources/results/app2.json index afb7d84b8..a0c5620f9 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app2.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app2.json @@ -1156,10 +1156,12 @@ "properties": { "id": { "type": "integer", + "description": "The Id.", "format": "int64" }, "name": { - "type": "string" + "type": "string", + "description": "The Name." } } }, @@ -1214,10 +1216,12 @@ "properties": { "id": { "type": "integer", + "description": "The Id.", "format": "int64" }, "name": { - "type": "string" + "type": "string", + "description": "The Name." } } }, diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app87.json b/springdoc-openapi-javadoc/src/test/resources/results/app87.json index 3b9078ccb..685d59b0f 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app87.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app87.json @@ -74,7 +74,8 @@ "components": { "schemas": { "Item": { - "type": "object" + "type": "object", + "description": "The type Item." } } } diff --git a/springdoc-openapi-javadoc/src/test/resources/results/app96.json b/springdoc-openapi-javadoc/src/test/resources/results/app96.json index 0959dae46..743d25b55 100644 --- a/springdoc-openapi-javadoc/src/test/resources/results/app96.json +++ b/springdoc-openapi-javadoc/src/test/resources/results/app96.json @@ -93,16 +93,17 @@ "summary": "Test 3 string.", "description": "Test 3 string.", "operationId": "test3", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "string" - } + "parameters": [ + { + "name": "test", + "in": "query", + "description": "the test", + "required": true, + "schema": { + "type": "string" } - }, - "required": true - }, + } + ], "responses": { "200": { "description": "the string", diff --git a/springdoc-openapi-kotlin/pom.xml b/springdoc-openapi-kotlin/pom.xml index 00f213275..195a7e7ef 100644 --- a/springdoc-openapi-kotlin/pom.xml +++ b/springdoc-openapi-kotlin/pom.xml @@ -3,7 +3,7 @@ org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 5.3.6 diff --git a/springdoc-openapi-security/pom.xml b/springdoc-openapi-security/pom.xml index 2132d8066..71b94a596 100644 --- a/springdoc-openapi-security/pom.xml +++ b/springdoc-openapi-security/pom.xml @@ -4,7 +4,7 @@ springdoc-openapi org.springdoc - 1.7.0 + 1.8.0 springdoc-openapi-security diff --git a/springdoc-openapi-ui/pom.xml b/springdoc-openapi-ui/pom.xml index 9e4cc00a3..6d3d29943 100644 --- a/springdoc-openapi-ui/pom.xml +++ b/springdoc-openapi-ui/pom.xml @@ -3,7 +3,7 @@ org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 springdoc-openapi-ui diff --git a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerIndexPageTransformer.java b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerIndexPageTransformer.java index b2f8f6635..2bdfc7748 100644 --- a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerIndexPageTransformer.java +++ b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerIndexPageTransformer.java @@ -23,6 +23,7 @@ package org.springdoc.webmvc.ui; import java.io.IOException; +import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletRequest; @@ -75,7 +76,7 @@ public Resource transform(HttpServletRequest request, Resource resource, if (isIndexFound) { String html = defaultTransformations(resource.getInputStream()); - return new TransformedResource(resource, html.getBytes()); + return new TransformedResource(resource, html.getBytes(StandardCharsets.UTF_8)); } else return resource; diff --git a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerUiHome.java b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerUiHome.java index 5f5034ca5..4903f75a8 100644 --- a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerUiHome.java +++ b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerUiHome.java @@ -23,7 +23,7 @@ package org.springdoc.webmvc.ui; import io.swagger.v3.oas.annotations.Operation; -import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.SpringDocUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; @@ -61,7 +61,7 @@ public class SwaggerUiHome { @Operation(hidden = true) public String index() { StringBuilder uiRootPath = new StringBuilder(); - if (StringUtils.isNotBlank(mvcServletPath)) + if (SpringDocUtils.isValidPath(mvcServletPath)) uiRootPath.append(mvcServletPath); return REDIRECT_URL_PREFIX + uiRootPath + swaggerUiPath; diff --git a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeActuator.java b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeActuator.java index 3dc953a5b..4fe608bda 100644 --- a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeActuator.java +++ b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeActuator.java @@ -40,7 +40,7 @@ import static org.springdoc.core.Constants.DEFAULT_API_DOCS_ACTUATOR_URL; import static org.springdoc.core.Constants.DEFAULT_SWAGGER_UI_ACTUATOR_PATH; -import static org.springdoc.core.Constants.SWAGGGER_CONFIG_FILE; +import static org.springdoc.core.Constants.SWAGGER_CONFIG_FILE; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** @@ -52,7 +52,7 @@ public class SwaggerWelcomeActuator extends SwaggerWelcomeCommon { /** * The constant SWAGGER_CONFIG_ACTUATOR_URL. */ - private static final String SWAGGER_CONFIG_ACTUATOR_URL = DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + private static final String SWAGGER_CONFIG_ACTUATOR_URL = DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; /** * The Web endpoint properties. @@ -119,7 +119,7 @@ protected String buildUrlWithContextPath(String swaggerUiUrl) { protected String buildSwaggerConfigUrl() { return contextPath + webEndpointProperties.getBasePath() + DEFAULT_PATH_SEPARATOR + DEFAULT_SWAGGER_UI_ACTUATOR_PATH - + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + + DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; } } diff --git a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java index 6abf920a2..86c41d54f 100644 --- a/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java +++ b/springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java @@ -25,8 +25,8 @@ import javax.servlet.http.HttpServletRequest; import io.swagger.v3.oas.annotations.Operation; -import org.apache.commons.lang3.StringUtils; import org.springdoc.core.SpringDocConfigProperties; +import org.springdoc.core.SpringDocUtils; import org.springdoc.core.SwaggerUiConfigParameters; import org.springdoc.core.SwaggerUiConfigProperties; import org.springdoc.core.providers.SpringWebProvider; @@ -37,8 +37,8 @@ import org.springframework.web.bind.annotation.GetMapping; import static org.springdoc.core.Constants.MVC_SERVLET_PATH; +import static org.springdoc.core.Constants.SWAGGER_CONFIG_FILE; import static org.springdoc.core.Constants.SWAGGER_UI_PATH; -import static org.springdoc.core.Constants.SWAGGGER_CONFIG_FILE; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** @@ -98,7 +98,7 @@ public ResponseEntity redirectToUi(HttpServletRequest request) { @Override protected void calculateUiRootPath(StringBuilder... sbUrls) { StringBuilder sbUrl = new StringBuilder(); - if (StringUtils.isNotBlank(mvcServletPath)) + if (SpringDocUtils.isValidPath(mvcServletPath)) sbUrl.append(mvcServletPath); calculateUiRootCommon(sbUrl, sbUrls); } @@ -112,7 +112,7 @@ protected void calculateUiRootPath(StringBuilder... sbUrls) { */ @Override protected String buildUrl(String contextPath, final String docsUrl) { - if (StringUtils.isNotBlank(mvcServletPath)) + if (SpringDocUtils.isValidPath(mvcServletPath)) contextPath += mvcServletPath; return super.buildUrl(contextPath, docsUrl); } @@ -141,7 +141,7 @@ protected String buildUrlWithContextPath(String swaggerUiUrl) { */ @Override protected String buildSwaggerConfigUrl() { - return apiDocsUrl + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + return apiDocsUrl + DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; } } \ No newline at end of file diff --git a/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigTest.java b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigTest.java index fc02c328f..728d0d103 100644 --- a/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigTest.java +++ b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigTest.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; import static org.hamcrest.CoreMatchers.equalTo; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -30,6 +31,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@DirtiesContext @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "spring.jackson.property-naming-strategy=UPPER_CAMEL_CASE", "springdoc.show-actuator=true", "management.endpoints.web.base-path=/management", diff --git a/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigWithBothFileGeneratedSpecsTest.java b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigWithBothFileGeneratedSpecsTest.java new file mode 100644 index 000000000..d32fd1d8f --- /dev/null +++ b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app1/SpringDocSwaggerConfigWithBothFileGeneratedSpecsTest.java @@ -0,0 +1,67 @@ +/* + * + * * 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.ui.app1; + +import org.junit.jupiter.api.Test; +import test.org.springdoc.ui.AbstractSpringDocActuatorTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/* + Test showing how specs generated at runtime and specs configured in can work at the same time. + + The expectation is that the openapi.yml file will be shown together with the generated ones. + */ +@DirtiesContext +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.jackson.property-naming-strategy=UPPER_CAMEL_CASE", "springdoc.show-actuator=true", + "management.endpoints.web.base-path=/management", + "server.servlet.context-path=/demo/api", "management.server.port=9002", "management.server.base-path=/demo/api", + "springdoc.swagger-ui.urls[0].url=/api-docs/xxx/v1/openapi.yml", + "springdoc.swagger-ui.urls[0].name=toto", +}) +public class SpringDocSwaggerConfigWithBothFileGeneratedSpecsTest extends AbstractSpringDocActuatorTest { + + @Test + public void testIndexSwaggerConfig() throws Exception { + mockMvc.perform(get("/demo/api/v3/api-docs/swagger-config").contextPath("/demo/api")) + .andExpect(status().isOk()) + .andExpect(jsonPath("validatorUrl", equalTo(""))) + .andExpect(jsonPath("oauth2RedirectUrl", equalTo("http://localhost/demo/api/swagger-ui/oauth2-redirect.html"))) + .andExpect(jsonPath("url").doesNotExist()) + .andExpect(jsonPath("urls[0].url", equalTo("/demo/api/v3/api-docs/springdocDefault"))) + .andExpect(jsonPath("urls[0].name", equalTo("springdocDefault"))) + .andExpect(jsonPath("urls[1].url", equalTo("/demo/api/api-docs/xxx/v1/openapi.yml"))) + .andExpect(jsonPath("urls[1].name", equalTo("toto"))) + .andExpect(jsonPath("urls[2].url", equalTo("/demo/api/v3/api-docs/x-actuator"))) + .andExpect(jsonPath("urls[2].name", equalTo("x-actuator"))); + } + + + @SpringBootApplication + static class SpringDocTestApp {} + +} \ No newline at end of file diff --git a/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/HelloController.java b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/HelloController.java new file mode 100644 index 000000000..fa2f03337 --- /dev/null +++ b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/HelloController.java @@ -0,0 +1,37 @@ +/* + * + * * 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.ui.app33; + + +import javax.validation.Valid; +import javax.validation.constraints.Size; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public void persons(@Valid @RequestParam @Size(min = 4, max = 6) String name) { + + } + +} diff --git a/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocApp33Test.java b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocApp33Test.java new file mode 100644 index 000000000..49e2382ce --- /dev/null +++ b/springdoc-openapi-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocApp33Test.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.ui.app33; + +import org.junit.jupiter.api.Test; +import test.org.springdoc.ui.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestPropertySource(properties = { + "server.servlet.context-path=/", + "spring.mvc.servlet.path=/" +}) +public class SpringDocApp33Test extends AbstractSpringDocTest { + + @Test + public void shouldRedirectWithConfigUrlIgnoringQueryParams() throws Exception { + mockMvc.perform(get("/swagger-ui.html")) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "/swagger-ui/index.html")); + + super.chekJS(); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} \ No newline at end of file diff --git a/springdoc-openapi-ui/src/test/resources/results/app33 b/springdoc-openapi-ui/src/test/resources/results/app33 new file mode 100644 index 000000000..671da656b --- /dev/null +++ b/springdoc-openapi-ui/src/test/resources/results/app33 @@ -0,0 +1,24 @@ +window.onload = function() { + // + + // the following lines will be replaced by docker/configurator, when it runs in a docker-container + window.ui = SwaggerUIBundle({ + url: "https://petstore.swagger.io/v2/swagger.json", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" , + + "configUrl" : "/v3/api-docs/swagger-config", + "validatorUrl" : "" + + }); + + // +}; diff --git a/springdoc-openapi-webflux-core/pom.xml b/springdoc-openapi-webflux-core/pom.xml index a2c70d732..432c5cb7e 100644 --- a/springdoc-openapi-webflux-core/pom.xml +++ b/springdoc-openapi-webflux-core/pom.xml @@ -3,7 +3,7 @@ org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 springdoc-openapi-webflux-core diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java index e07a11bc7..cd3dfa203 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.bind.annotation.GetMapping; @@ -46,6 +47,7 @@ import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML; import static org.springdoc.core.Constants.DEFAULT_API_DOCS_ACTUATOR_URL; import static org.springdoc.core.Constants.DEFAULT_YAML_API_DOCS_ACTUATOR_PATH; +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; import static org.springdoc.core.Constants.YAML; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; @@ -55,6 +57,7 @@ * @author bnasslashen */ @RestControllerEndpoint(id = DEFAULT_API_DOCS_ACTUATOR_URL) +@ConditionalOnProperty(name = SPRINGDOC_ENABLE_DEFAULT_API_DOCS, havingValue = "true", matchIfMissing = true) public class OpenApiActuatorResource extends OpenApiResource { /** diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiWebfluxResource.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiWebfluxResource.java index a812be7e0..9cd30143c 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiWebfluxResource.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiWebfluxResource.java @@ -41,6 +41,7 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.bind.annotation.GetMapping; @@ -49,12 +50,14 @@ import static org.springdoc.core.Constants.API_DOCS_URL; import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML; import static org.springdoc.core.Constants.DEFAULT_API_DOCS_URL_YAML; +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; /** * The type Open api resource. * @author bnasslahsen */ @RestController +@ConditionalOnProperty(name = SPRINGDOC_ENABLE_DEFAULT_API_DOCS, havingValue = "true", matchIfMissing = true) public class OpenApiWebfluxResource extends OpenApiResource { diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java index ac12011f0..55b533087 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/SpringDocWebFluxConfiguration.java @@ -91,6 +91,7 @@ public class SpringDocWebFluxConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = SPRINGDOC_USE_MANAGEMENT_PORT, havingValue = "false", matchIfMissing = true) + @ConditionalOnExpression("(${springdoc.use-management-port:false} == false ) and ${springdoc.enable-default-api-docs:true}") @Lazy(false) OpenApiWebfluxResource openApiResource(ObjectFactory openAPIBuilderObjectFactory, AbstractRequestService requestBuilder, GenericResponseService responseBuilder, OperationService operationParser, @@ -200,6 +201,7 @@ ActuatorProvider actuatorProvider(ServerProperties serverProperties, @Bean @ConditionalOnMissingBean(MultipleOpenApiSupportConfiguration.class) @ConditionalOnProperty(SPRINGDOC_USE_MANAGEMENT_PORT) + @ConditionalOnExpression("${springdoc.use-management-port:false} and ${springdoc.enable-default-api-docs:true}") @ConditionalOnManagementPort(ManagementPortType.DIFFERENT) @Lazy(false) OpenApiActuatorResource actuatorOpenApiResource(ObjectFactory openAPIBuilderObjectFactory, AbstractRequestService requestBuilder, diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/fn/SpringdocRouteBuilder.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/fn/SpringdocRouteBuilder.java index 00208075d..337a34b08 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/fn/SpringdocRouteBuilder.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/core/fn/SpringdocRouteBuilder.java @@ -649,12 +649,10 @@ public SpringdocRouteBuilder after(BiFunction predicate, BiFunction> responseProvider, Consumer operationsConsumer) { - Builder builder = getOperationBuilder(operationsConsumer); - this.delegate.onError(predicate, responseProvider).withAttribute(OPERATION_ATTRIBUTE, builder); + public SpringdocRouteBuilder onError(Predicate predicate, BiFunction> responseProvider) { + this.delegate.onError(predicate, responseProvider); return this; } @@ -665,12 +663,10 @@ public SpringdocRouteBuilder onError(Predicate predicate, BiF * @param the type parameter * @param exceptionType the exception type * @param responseProvider the response provider - * @param operationsConsumer the operations consumer * @return the springdoc route builder */ - public SpringdocRouteBuilder onError(Class exceptionType, BiFunction> responseProvider, Consumer operationsConsumer) { - Builder builder = getOperationBuilder(operationsConsumer); - this.delegate.onError(exceptionType, responseProvider).withAttribute(OPERATION_ATTRIBUTE, builder); + public SpringdocRouteBuilder onError(Class exceptionType, BiFunction> responseProvider) { + this.delegate.onError(exceptionType, responseProvider); return this; } diff --git a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app145/SpringDocApp1451Test.java b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app145/SpringDocApp1451Test.java index 940e500cf..2b09a3ff8 100644 --- a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app145/SpringDocApp1451Test.java +++ b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app145/SpringDocApp1451Test.java @@ -25,6 +25,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.http.HttpStatus; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.web.reactive.function.client.WebClientResponseException; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -32,6 +33,7 @@ import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +@DirtiesContext @SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT, properties = { "management.endpoints.web.exposure.include=*", "server.port=55594", diff --git a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190Test.java b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190Test.java new file mode 100644 index 000000000..8c6ec67e4 --- /dev/null +++ b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190Test.java @@ -0,0 +1,44 @@ +/* + * + * * 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.app190; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.Constants; +import test.org.springdoc.api.AbstractCommonTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.context.annotation.ComponentScan; + +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; + + +@WebFluxTest(properties = SPRINGDOC_ENABLE_DEFAULT_API_DOCS+"=false") +public class SpringDocApp190Test extends AbstractCommonTest { + + @SpringBootApplication + @ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app190" }) + static class SpringDocTestApp {} + + @Test + public void test_disable_default_api_docs() throws Exception { + webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL).exchange() + .expectStatus().isNotFound(); + } +} \ No newline at end of file diff --git a/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190bisTest.java b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190bisTest.java new file mode 100644 index 000000000..a8dd65246 --- /dev/null +++ b/springdoc-openapi-webflux-core/src/test/java/test/org/springdoc/api/app190/SpringDocApp190bisTest.java @@ -0,0 +1,43 @@ +/* + * + * * 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.app190; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.Constants; +import test.org.springdoc.api.AbstractCommonTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.context.annotation.ComponentScan; + +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; + +@WebFluxTest(properties = SPRINGDOC_ENABLE_DEFAULT_API_DOCS+"=true") +public class SpringDocApp190bisTest extends AbstractCommonTest { + + @SpringBootApplication + @ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app190" }) + static class SpringDocTestApp {} + + @Test + public void test_enable_default_api_docs() throws Exception { + webTestClient.get().uri(Constants.DEFAULT_API_DOCS_URL).exchange() + .expectStatus().isOk(); + } +} \ No newline at end of file diff --git a/springdoc-openapi-webflux-ui/pom.xml b/springdoc-openapi-webflux-ui/pom.xml index c6e908e3c..3cde80be7 100644 --- a/springdoc-openapi-webflux-ui/pom.xml +++ b/springdoc-openapi-webflux-ui/pom.xml @@ -3,7 +3,7 @@ org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 springdoc-openapi-webflux-ui diff --git a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java index 01a5114e7..d88a2557e 100644 --- a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java +++ b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java @@ -22,6 +22,8 @@ package org.springdoc.webflux.ui; +import java.nio.charset.StandardCharsets; + import org.springdoc.core.SwaggerUiConfigParameters; import org.springdoc.core.SwaggerUiConfigProperties; import org.springdoc.core.SwaggerUiOAuthProperties; @@ -73,7 +75,7 @@ public Mono transform(ServerWebExchange serverWebExchange, Resource re boolean isIndexFound = antPathMatcher.match("**/swagger-ui/**/" + SWAGGER_INITIALIZER_JS, resource.getURL().toString()); if (isIndexFound) { String html = defaultTransformations(resource.getInputStream()); - return Mono.just(new TransformedResource(resource, html.getBytes())); + return Mono.just(new TransformedResource(resource, html.getBytes(StandardCharsets.UTF_8))); } else { return Mono.just(resource); diff --git a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerResourceResolver.java b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerResourceResolver.java index 7eb634813..481e8b0cc 100644 --- a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerResourceResolver.java +++ b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerResourceResolver.java @@ -30,23 +30,29 @@ public SwaggerResourceResolver(SwaggerUiConfigProperties swaggerUiConfigProperti @Override public Mono resolveResource(ServerWebExchange exchange, String requestPath, List locations, ResourceResolverChain chain) { - Mono resolved = chain.resolveResource(exchange, requestPath, locations); - if (!Mono.empty().equals(resolved)) { - String webJarResourcePath = findWebJarResourcePath(requestPath); - if (webJarResourcePath != null) - return chain.resolveResource(exchange, webJarResourcePath, locations); - } - return resolved; + return chain.resolveResource(exchange, requestPath, locations) + .switchIfEmpty(Mono.defer(() -> { + String webJarsResourcePath = findWebJarResourcePath(requestPath); + if (webJarsResourcePath != null) { + return chain.resolveResource(exchange, webJarsResourcePath, locations); + } + else { + return Mono.empty(); + } + })); } @Override - public Mono resolveUrlPath(String resourcePath, List locations, ResourceResolverChain chain) { - Mono path = chain.resolveUrlPath(resourcePath, locations); - if (!Mono.empty().equals(path)) { - String webJarResourcePath = findWebJarResourcePath(resourcePath); - if (webJarResourcePath != null) - return chain.resolveUrlPath(webJarResourcePath, locations); - } - return path; + public Mono resolveUrlPath(String resourceUrlPath, List locations, ResourceResolverChain chain) { + return chain.resolveUrlPath(resourceUrlPath, locations) + .switchIfEmpty(Mono.defer(() -> { + String webJarResourcePath = findWebJarResourcePath(resourceUrlPath); + if (webJarResourcePath != null) { + return chain.resolveUrlPath(webJarResourcePath, locations); + } + else { + return Mono.empty(); + } + })); } } \ No newline at end of file diff --git a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java index 176876abe..b5ba0cb4e 100644 --- a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java +++ b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java @@ -43,7 +43,7 @@ import static org.springdoc.core.Constants.DEFAULT_API_DOCS_ACTUATOR_URL; import static org.springdoc.core.Constants.DEFAULT_SWAGGER_UI_ACTUATOR_PATH; -import static org.springdoc.core.Constants.SWAGGGER_CONFIG_FILE; +import static org.springdoc.core.Constants.SWAGGER_CONFIG_FILE; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** @@ -56,7 +56,7 @@ public class SwaggerWelcomeActuator extends SwaggerWelcomeCommon { /** * The constant SWAGGER_CONFIG_ACTUATOR_URL. */ - private static final String SWAGGER_CONFIG_ACTUATOR_URL = DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + private static final String SWAGGER_CONFIG_ACTUATOR_URL = DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; /** * The Web endpoint properties. @@ -126,8 +126,8 @@ protected void calculateUiRootPath(StringBuilder... sbUrls) { @Override protected void calculateOauth2RedirectUrl(UriComponentsBuilder uriComponentsBuilder) { if (StringUtils.isBlank(swaggerUiConfig.getOauth2RedirectUrl()) || !swaggerUiConfigParameters.isValidUrl(swaggerUiConfig.getOauth2RedirectUrl())) { - this.oauthPrefix = uriComponentsBuilder.path(managementServerProperties.getBasePath() + swaggerUiConfigParameters.getUiRootPath()).path(webJarsPrefixUrl); - swaggerUiConfigParameters.setOauth2RedirectUrl(this.oauthPrefix.path(getOauth2RedirectUrl()).build().toString()); + UriComponentsBuilder oauthPrefix = uriComponentsBuilder.path(managementServerProperties.getBasePath() + swaggerUiConfigParameters.getUiRootPath()).path(webJarsPrefixUrl); + swaggerUiConfigParameters.setOauth2RedirectUrl(oauthPrefix.path(getOauth2RedirectUrl()).build().toString()); } } @@ -145,7 +145,7 @@ protected String buildUrlWithContextPath(String swaggerUiUrl) { protected String buildSwaggerConfigUrl() { return contextPath + webEndpointProperties.getBasePath() + DEFAULT_PATH_SEPARATOR + DEFAULT_SWAGGER_UI_ACTUATOR_PATH - + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + + DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; } } diff --git a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java index 7e2fb2e3d..5f12e1c80 100644 --- a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java +++ b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java @@ -48,11 +48,6 @@ public abstract class SwaggerWelcomeCommon extends AbstractSwaggerWelcome { */ protected String webJarsPrefixUrl; - /** - * The Oauth prefix. - */ - protected UriComponentsBuilder oauthPrefix; - /** * Instantiates a new Abstract swagger welcome. * @param swaggerUiConfig the swagger ui config diff --git a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java index c1ce011a6..d0606b548 100644 --- a/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java +++ b/springdoc-openapi-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java @@ -36,8 +36,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.util.UriComponentsBuilder; +import static org.springdoc.core.Constants.SWAGGER_CONFIG_FILE; import static org.springdoc.core.Constants.SWAGGER_UI_PATH; -import static org.springdoc.core.Constants.SWAGGGER_CONFIG_FILE; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** @@ -106,8 +106,8 @@ protected void calculateUiRootPath(StringBuilder... sbUrls) { @Override protected void calculateOauth2RedirectUrl(UriComponentsBuilder uriComponentsBuilder) { if (StringUtils.isBlank(swaggerUiConfig.getOauth2RedirectUrl()) || !swaggerUiConfigParameters.isValidUrl(swaggerUiConfig.getOauth2RedirectUrl())) { - this.oauthPrefix = uriComponentsBuilder.path(contextPath).path(swaggerUiConfigParameters.getUiRootPath()).path(webJarsPrefixUrl); - swaggerUiConfigParameters.setOauth2RedirectUrl(this.oauthPrefix.path(getOauth2RedirectUrl()).build().toString()); + UriComponentsBuilder oauthPrefix = uriComponentsBuilder.path(contextPath).path(swaggerUiConfigParameters.getUiRootPath()).path(webJarsPrefixUrl); + swaggerUiConfigParameters.setOauth2RedirectUrl(oauthPrefix.path(getOauth2RedirectUrl()).build().toString()); } } @@ -135,7 +135,7 @@ protected String buildUrlWithContextPath(String swaggerUiUrl) { */ @Override protected String buildSwaggerConfigUrl() { - return this.apiDocsUrl + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE; + return this.apiDocsUrl + DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; } } diff --git a/springdoc-openapi-webmvc-core/pom.xml b/springdoc-openapi-webmvc-core/pom.xml index a64d1484e..9183ef280 100644 --- a/springdoc-openapi-webmvc-core/pom.xml +++ b/springdoc-openapi-webmvc-core/pom.xml @@ -3,7 +3,7 @@ org.springdoc springdoc-openapi - 1.7.0 + 1.8.0 springdoc-openapi-webmvc-core diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiActuatorResource.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiActuatorResource.java index 034596238..f4e3416bf 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiActuatorResource.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiActuatorResource.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; @@ -45,6 +46,7 @@ import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML; import static org.springdoc.core.Constants.DEFAULT_API_DOCS_ACTUATOR_URL; import static org.springdoc.core.Constants.DEFAULT_YAML_API_DOCS_ACTUATOR_PATH; +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; import static org.springdoc.core.Constants.YAML; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; @@ -54,6 +56,7 @@ * @author bnasslashen */ @RestControllerEndpoint(id = DEFAULT_API_DOCS_ACTUATOR_URL) +@ConditionalOnProperty(name = SPRINGDOC_ENABLE_DEFAULT_API_DOCS, havingValue = "true", matchIfMissing = true) public class OpenApiActuatorResource extends OpenApiResource { /** diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiWebMvcResource.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiWebMvcResource.java index 222b49426..8f04c2e07 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiWebMvcResource.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiWebMvcResource.java @@ -42,6 +42,7 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -49,12 +50,14 @@ import static org.springdoc.core.Constants.API_DOCS_URL; import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML; import static org.springdoc.core.Constants.DEFAULT_API_DOCS_URL_YAML; +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; /** * The type Open api resource. * @author bnasslahsen */ @RestController +@ConditionalOnProperty(name = SPRINGDOC_ENABLE_DEFAULT_API_DOCS, havingValue = "true", matchIfMissing = true) public class OpenApiWebMvcResource extends OpenApiResource { /** diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java index a72c0e68b..d9d6d7c8b 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/SpringDocWebMvcConfiguration.java @@ -100,6 +100,7 @@ public class SpringDocWebMvcConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = SPRINGDOC_USE_MANAGEMENT_PORT, havingValue = "false", matchIfMissing = true) + @ConditionalOnExpression("(${springdoc.use-management-port:false} == false ) and ${springdoc.enable-default-api-docs:true}") @Lazy(false) OpenApiWebMvcResource openApiResource(ObjectFactory openAPIBuilderObjectFactory, AbstractRequestService requestBuilder, GenericResponseService responseBuilder, OperationService operationParser, @@ -228,6 +229,7 @@ ActuatorProvider actuatorProvider(ServerProperties serverProperties, @Bean @ConditionalOnMissingBean(MultipleOpenApiSupportConfiguration.class) @ConditionalOnProperty(SPRINGDOC_USE_MANAGEMENT_PORT) + @ConditionalOnExpression("${springdoc.use-management-port:false} and ${springdoc.enable-default-api-docs:true}") @ConditionalOnManagementPort(ManagementPortType.DIFFERENT) @Lazy(false) OpenApiActuatorResource openApiActuatorResource(ObjectFactory openAPIBuilderObjectFactory, AbstractRequestService requestBuilder, diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/fn/SpringdocRouteBuilder.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/fn/SpringdocRouteBuilder.java index 10f7795c9..4ca0f36b8 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/fn/SpringdocRouteBuilder.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/core/fn/SpringdocRouteBuilder.java @@ -649,12 +649,10 @@ public SpringdocRouteBuilder after(BiFunction predicate, BiFunction responseProvider, Consumer operationsConsumer) { - Builder builder = getOperationBuilder(operationsConsumer); - this.delegate.onError(predicate, responseProvider).withAttribute(OPERATION_ATTRIBUTE, builder); + public SpringdocRouteBuilder onError(Predicate predicate, BiFunction responseProvider) { + this.delegate.onError(predicate, responseProvider); return this; } @@ -665,12 +663,10 @@ public SpringdocRouteBuilder onError(Predicate predicate, BiFunction< * @param the type parameter * @param exceptionType the exception type * @param responseProvider the response provider - * @param operationsConsumer the operations consumer * @return the springdoc route builder */ - public SpringdocRouteBuilder onError(Class exceptionType, BiFunction responseProvider, Consumer operationsConsumer) { - Builder builder = getOperationBuilder(operationsConsumer); - this.delegate.onError(exceptionType, responseProvider).withAttribute(OPERATION_ATTRIBUTE, builder); + public SpringdocRouteBuilder onError(Class exceptionType, BiFunction responseProvider) { + this.delegate.onError(exceptionType, responseProvider); return this; } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app110/PersonController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app110/PersonController.java index 4193cfc2e..fd6a32eba 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app110/PersonController.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app110/PersonController.java @@ -32,6 +32,7 @@ import javax.validation.constraints.Size; import org.springframework.validation.annotation.Validated; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -44,7 +45,7 @@ public class PersonController { private Random ran = new Random(); @RequestMapping(path = "/person", method = RequestMethod.POST) - public Person person(@Valid @RequestBody Person person) { + public Person person(@Valid @RequestBody Person person) throws HttpMediaTypeNotSupportedException { int nxt = ran.nextInt(10); if (nxt >= 5) { @@ -56,7 +57,7 @@ public Person person(@Valid @RequestBody Person person) { @RequestMapping(path = "/personByLastName", method = RequestMethod.GET) public List findByLastName(@RequestParam(name = "lastName", required = true) @NotNull @NotBlank - @Size(max = 10) String lastName) { + @Size(max = 10) String lastName) throws HttpMediaTypeNotSupportedException { List hardCoded = new ArrayList<>(); Person person = new Person(); person.setAge(20); diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app112/sample/PersonController2.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app112/sample/PersonController2.java index d6da662cf..3e14e8c59 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app112/sample/PersonController2.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app112/sample/PersonController2.java @@ -34,6 +34,7 @@ import test.org.springdoc.api.v30.app112.Person; import org.springframework.validation.annotation.Validated; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -46,7 +47,7 @@ public class PersonController2 { private Random ran = new Random(); @RequestMapping(path = "/person2", method = RequestMethod.POST) - public Person person(@Valid @RequestBody Person person) { + public Person person(@Valid @RequestBody Person person) throws HttpMediaTypeNotSupportedException { int nxt = ran.nextInt(10); if (nxt >= 5) { @@ -58,7 +59,7 @@ public Person person(@Valid @RequestBody Person person) { @RequestMapping(path = "/personByLastName2", method = RequestMethod.GET) public List findByLastName(@RequestParam(name = "lastName", required = true) @NotNull @NotBlank - @Size(max = 10) String lastName) { + @Size(max = 10) String lastName) throws HttpMediaTypeNotSupportedException { List hardCoded = new ArrayList<>(); Person person = new Person(); person.setAge(20); diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app138/SpringDocApp138Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app138/SpringDocApp138Test.java index c8edb6dcf..812b6c41d 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app138/SpringDocApp138Test.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app138/SpringDocApp138Test.java @@ -86,7 +86,7 @@ public void testApp() throws Exception { } private static class SpringDocObjectMapperFactory extends ObjectMapperFactory { - protected static ObjectMapper createJson() { + public static ObjectMapper createJson() { return ObjectMapperFactory.createJson(); } } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app173/SpringDocApp173Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app173/SpringDocApp173Test.java index 1990a052b..dee6d20cb 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app173/SpringDocApp173Test.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app173/SpringDocApp173Test.java @@ -26,6 +26,7 @@ import java.util.Locale; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; import org.junit.jupiter.api.Test; import org.springdoc.core.Constants; import test.org.springdoc.api.v30.AbstractSpringDocV30Test; @@ -66,7 +67,9 @@ private void testApp(Locale locale) throws Exception { static class SpringDocTestApp { @Bean public OpenAPI openAPI() { - return new OpenAPI().extensions(Collections.singletonMap("TEST", "HELLO")); + return new OpenAPI() + .info(new Info().extensions( Collections.singletonMap("TEST", "HELLO"))) + .extensions(Collections.singletonMap("TEST", "HELLO")); } } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app188/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app188/HelloController.java index 3f96d320a..68571ddfd 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app188/HelloController.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app188/HelloController.java @@ -30,6 +30,7 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.responses.ApiResponse; import org.springdoc.core.SpringDocAnnotationsUtils; @@ -65,7 +66,7 @@ public OperationCustomizer operationCustomizer(OpenAPI api) { api.getComponents(), ErrorResponse.class, null, - null + null, SpecVersion.V30 ); ApiResponse errorApiResponse = new ApiResponse().content(new Content().addMediaType( diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/Example2Controller.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/Example2Controller.java index 96cef2e8f..ddc30e875 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/Example2Controller.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/Example2Controller.java @@ -1,6 +1,7 @@ package test.org.springdoc.api.v30.app197; import org.springframework.http.HttpStatus; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,7 +12,7 @@ @RequestMapping("/example2") public class Example2Controller { @GetMapping("/") - public void index() { + public void index() throws HttpRequestMethodNotSupportedException { throw new IllegalArgumentException(); } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/ExampleController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/ExampleController.java index 7e5e6d0ed..d4c1f2a02 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/ExampleController.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app197/ExampleController.java @@ -1,6 +1,7 @@ package test.org.springdoc.api.v30.app197; import org.springframework.http.HttpStatus; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,7 +12,7 @@ @RequestMapping("/example") public class ExampleController { @GetMapping("/") - public void index() { + public void index()throws HttpRequestMethodNotSupportedException { throw new IllegalArgumentException(); } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app199/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app199/HelloController.java index 6054b7dcc..55372f4a0 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app199/HelloController.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app199/HelloController.java @@ -30,6 +30,7 @@ import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.examples.Example; import org.springdoc.core.customizers.OperationCustomizer; +import test.org.springdoc.api.v30.app199.CustomExceptionHandler.MyInternalException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -50,13 +51,13 @@ public class HelloController { value = "/first", produces = APPLICATION_JSON_VALUE ) - public void first() {} + public void first() throws MyInternalException {} @GetMapping( value = "/second", produces = APPLICATION_JSON_VALUE ) - public void second() {} + public void second() throws MyInternalException {} @Bean public OperationCustomizer operationCustomizer() diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/Example2Controller.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/Example2Controller.java index 522df6183..fdc9c0a93 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/Example2Controller.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/Example2Controller.java @@ -1,6 +1,7 @@ package test.org.springdoc.api.v30.app202; import org.springframework.http.HttpStatus; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,7 +12,7 @@ @RequestMapping("/example2") public class Example2Controller { @GetMapping("/") - public void index() { + public void index() throws HttpRequestMethodNotSupportedException { throw new IllegalArgumentException(); } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/ExampleController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/ExampleController.java index 930efbcb8..8c39846b5 100644 --- a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/ExampleController.java +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app202/ExampleController.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,7 +14,7 @@ @Validated public class ExampleController { @GetMapping("/") - public void index() { + public void index() throws HttpRequestMethodNotSupportedException { throw new IllegalArgumentException(); } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonRequest.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonRequest.java new file mode 100644 index 000000000..491ea40b2 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonRequest.java @@ -0,0 +1,55 @@ +package test.org.springdoc.api.v30.app206; + +import java.util.Optional; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public class PersonRequest { + @Schema(requiredMode = RequiredMode.REQUIRED) + private long id; + + @Schema(requiredMode = RequiredMode.NOT_REQUIRED) + private String firstName; + + @NotBlank + @Size(max = 10) + private String lastName; + + @Schema(requiredMode = RequiredMode.AUTO) + private Optional email; + + @Schema(required = true) + private int age; + + public PersonRequest(long id, String firstName, String lastName, Optional email, int age) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.age = age; + } + + public long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public Optional getEmail() { + return email; + } + + public int getAge() { + return age; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonResponse.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonResponse.java new file mode 100644 index 000000000..42ad1ae12 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/PersonResponse.java @@ -0,0 +1,55 @@ +package test.org.springdoc.api.v30.app206; + +import java.util.Optional; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public class PersonResponse { + @Schema + private long id; + + @NotBlank + @Size(max = 10) + private String firstName; + + @Schema(requiredMode = RequiredMode.REQUIRED) + private String lastName; + + @Schema(requiredMode = RequiredMode.REQUIRED) + private Optional email; + + @Schema(required = true) + private int age; + + public PersonResponse(long id, String firstName, String lastName, Optional email, int age) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.age = age; + } + + public long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public Optional getEmail() { + return email; + } + + public int getAge() { + return age; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/RequiredModeController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/RequiredModeController.java new file mode 100644 index 000000000..df887ecac --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/RequiredModeController.java @@ -0,0 +1,14 @@ +package test.org.springdoc.api.v30.app206; + +import org.springdoc.api.annotations.ParameterObject; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RequiredModeController { + @GetMapping + public PersonResponse index(@ParameterObject PersonRequest request) { + return null; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/SpringdocApp206Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/SpringdocApp206Test.java new file mode 100644 index 000000000..5165dcf45 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app206/SpringdocApp206Test.java @@ -0,0 +1,12 @@ +package test.org.springdoc.api.v30.app206; + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +public class SpringdocApp206Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/SpringdocApp207Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/SpringdocApp207Test.java new file mode 100644 index 000000000..1d6e3ebbf --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/SpringdocApp207Test.java @@ -0,0 +1,12 @@ +package test.org.springdoc.api.v30.app207; + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +public class SpringdocApp207Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SuperController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SuperController.java new file mode 100644 index 000000000..284ac4876 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SuperController.java @@ -0,0 +1,20 @@ +package test.org.springdoc.api.v30.app207.controller; + +import java.util.ArrayList; +import java.util.List; + +import test.org.springdoc.api.v30.app207.model.SuperEntity; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +public abstract class SuperController> { + + @GetMapping({"page/{current}/{size}", "page"}) + public List findPage(@PathVariable(required = false) Long current, + @PathVariable(required = false) Long size, + @RequestParam(required = false) T params) { + return new ArrayList<>(); + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SysUserController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SysUserController.java new file mode 100644 index 000000000..ab71579d5 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/controller/SysUserController.java @@ -0,0 +1,14 @@ +package test.org.springdoc.api.v30.app207.controller; + + +import test.org.springdoc.api.v30.app207.model.SysUser; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/sysUser") +public class SysUserController extends SuperController { + + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SuperEntity.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SuperEntity.java new file mode 100644 index 000000000..1bd9e8ac1 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SuperEntity.java @@ -0,0 +1,5 @@ +package test.org.springdoc.api.v30.app207.model; + +public class SuperEntity { + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SysUser.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SysUser.java new file mode 100644 index 000000000..5db3b0b57 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app207/model/SysUser.java @@ -0,0 +1,4 @@ +package test.org.springdoc.api.v30.app207.model; + +public class SysUser extends SuperEntity { +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/HelloController.java new file mode 100644 index 000000000..dab741d16 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/HelloController.java @@ -0,0 +1,26 @@ +package test.org.springdoc.api.v30.app208; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author bnasslahsen + */ + +@RestController +@RequestMapping("/test1") +public class HelloController { + + @GetMapping + public String test1(RequestObject object) { + return null; + } + + @PostMapping + public String test2(@RequestBody RequestObject obj) { + return null; + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/RequestObject.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/RequestObject.java new file mode 100644 index 000000000..470ce0daa --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/RequestObject.java @@ -0,0 +1,26 @@ +package test.org.springdoc.api.v30.app208; + +/** + * @author bnasslahsen + */ +public class RequestObject { + private String id; + + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/SpringdocApp208Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/SpringdocApp208Test.java new file mode 100644 index 000000000..d38de30ed --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app208/SpringdocApp208Test.java @@ -0,0 +1,14 @@ +package test.org.springdoc.api.v30.app208; + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = { "springdoc.default-flat-param-object=true" }) +public class SpringdocApp208Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/HelloController.java new file mode 100644 index 000000000..aa40c00fb --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/HelloController.java @@ -0,0 +1,71 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 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.v30.app209; + +import javax.validation.constraints.NegativeOrZero; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.PositiveOrZero; + +import io.swagger.v3.oas.annotations.Parameter; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public String persons(@NotBlank String name) { + return "OK"; + } + + @GetMapping(value = "/persons2") + public String persons2(@NotBlank @Parameter(description = "persons name") String name) { + return "OK"; + } + + @GetMapping(value = "/persons3") + public String persons3(@NotBlank @Parameter(description = "persons name") @RequestParam String name) { + return "OK"; + } + + @GetMapping(value = "/persons4") + public String persons4(@PositiveOrZero int age) { + return "OK"; + } + + @GetMapping(value = "/persons5") + public String persons5(@NegativeOrZero int age) { + return "OK"; + } + + @GetMapping(value = "/persons6") + public String persons6(@NotEmpty @Parameter(description = "persons name") String name) { + return "OK"; + } + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/SpringDocApp209Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/SpringDocApp209Test.java new file mode 100644 index 000000000..4697d4652 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app209/SpringDocApp209Test.java @@ -0,0 +1,114 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 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.v30.app209; + +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import io.swagger.v3.oas.models.OpenAPI; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springdoc.core.Constants; +import org.springdoc.core.OpenAPIService; +import org.springdoc.core.PropertyResolverUtils; +import org.springdoc.core.SecurityService; +import org.springdoc.core.SpringDocConfigProperties; +import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.ServerBaseUrlCustomizer; +import org.springdoc.core.providers.JavadocProvider; +import test.org.springdoc.api.AbstractCommonTest; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpHeaders; +import org.springframework.test.web.servlet.MvcResult; + +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(properties = { + "springdoc.pre-loading-enabled=true", + "springdoc.pre-loading-locales=ja" +}) +public class SpringDocApp209Test extends AbstractCommonTest { + public static String className; + + static { + System.setProperty("user.country", "JP"); + System.setProperty("user.language", "ja"); + } + + @Autowired + private OpenAPIServiceMock openAPIService; + + @SpringBootApplication + static class SpringDocTestApp { + @Bean("openAPIService") + public OpenAPIServiceMock openAPIService(Optional openAPI, SecurityService securityParser, SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils, Optional> openApiBuilderCustomizers, Optional> serverBaseUrlCustomizers, Optional javadocProvider) { + return new OpenAPIServiceMock(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); + } + } + + public static class OpenAPIServiceMock extends OpenAPIService { + private int numberOfTimesCalculatePathWasCalled; + + public OpenAPIServiceMock(Optional openAPI, SecurityService securityParser, SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils, Optional> openApiBuilderCustomizers, Optional> serverBaseUrlCustomizers, Optional javadocProvider) { + super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider); + } + + @Override + public void setCachedOpenAPI(OpenAPI cachedOpenAPI, Locale locale) { + numberOfTimesCalculatePathWasCalled++; + super.setCachedOpenAPI(cachedOpenAPI, locale); + } + + public int getNumberOfTimesCalculatePathWasCalled() { + return numberOfTimesCalculatePathWasCalled; + } + } + + @Test + public void shouldOnlyByCalledOnce() throws Exception { + //assertEquals(1, openAPIService.getNumberOfTimesCalculatePathWasCalled()); + + className = getClass().getSimpleName(); + String testNumber = className.replaceAll("[^0-9]", ""); + MvcResult mockMvcResult = mockMvc + .perform(get(Constants.DEFAULT_API_DOCS_URL).header(HttpHeaders.ACCEPT_LANGUAGE, "ja")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.openapi", is("3.0.1"))).andReturn(); + String result = mockMvcResult.getResponse().getContentAsString(); + String expected = getContent("results/3.0.1/app" + testNumber + ".json"); + JSONAssert.assertEquals(expected, result, true); + + assertEquals(1, openAPIService.getNumberOfTimesCalculatePathWasCalled()); + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app210/SpringDocApp210Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app210/SpringDocApp210Test.java new file mode 100644 index 000000000..158131768 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app210/SpringDocApp210Test.java @@ -0,0 +1,47 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 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.v30.app210; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.GroupedOpenApi; +import test.org.springdoc.api.AbstractCommonTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +public class SpringDocApp210Test extends AbstractCommonTest { + + @Test + public void testApp(){ + assertThrows(IllegalArgumentException.class, () -> GroupedOpenApi.builder().group("").build()); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java new file mode 100644 index 000000000..8ca1b0ca8 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java @@ -0,0 +1,101 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 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.v30.app211; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.SchemaProperty; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + @ApiResponses(value = @ApiResponse( + responseCode = "200", + content = { + @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schemaProperties = { + @SchemaProperty( + name = "items", + array = @ArraySchema(schema = @Schema(implementation = PagedObject.class))), + @SchemaProperty(name = "paging", schema = @Schema(implementation = Paging.class)) + } + ) + } + )) + @GetMapping + public String index() { + return null; + } + + public class PagedObject { + private long id; + private String name; + + public PagedObject(long id, String name) { + this.id = id; + this.name = name; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + } + + public class Paging { + private int page; + private int total; + private int lastPage; + + public Paging(int page, int total, int lastPage) { + this.page = page; + this.total = total; + this.lastPage = lastPage; + } + + public int getPage() { + return page; + } + + public int getTotal() { + return total; + } + + public int getLastPage() { + return lastPage; + } + } + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java new file mode 100644 index 000000000..31b1b5ef7 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java @@ -0,0 +1,36 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 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.v30.app211; + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +public class SpringDocApp211Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/HelloController.java new file mode 100644 index 000000000..ac920cd55 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/HelloController.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2019-2023 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.v30.app212; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public PersonDTO persons() { + return new PersonDTO("John"); + } + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/PersonDTO.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/PersonDTO.java new file mode 100644 index 000000000..5da4909f7 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/PersonDTO.java @@ -0,0 +1,31 @@ +/* + * + * * Copyright 2019-2023 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.v30.app212; + +public class PersonDTO { + private final String name; + + public PersonDTO(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/SpringDocApp212Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/SpringDocApp212Test.java new file mode 100644 index 000000000..db4c7cf0f --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app212/SpringDocApp212Test.java @@ -0,0 +1,66 @@ +/* + * + * * Copyright 2019-2023 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.v30.app212; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.GroupedOpenApi; +import org.springdoc.core.customizers.SpecPropertiesCustomizer; +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ActiveProfiles; + +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * The type Spring doc app 192 test. + *

+ * A test for {@link SpecPropertiesCustomizer} + */ +@ActiveProfiles("212") +public class SpringDocApp212Test extends AbstractSpringDocV30Test { + + /** + * The type Spring doc test app. + */ + @SpringBootApplication + static class SpringDocTestApp { + + @Bean + GroupedOpenApi apiGroupBeanName() { + return GroupedOpenApi.builder() + .group("apiGroupName") + .packagesToScan("test.org.springdoc.api.v30.app212") + .build(); + } + } + + @Test + void getGroupedOpenapi_shouldCustomizeFromPropertiesWithGroupNamePrefix() throws Exception { + String result = mockMvc.perform(get("/v3/api-docs/apiGroupName")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + String expected = getContent("results/3.0.1/app212-grouped.json"); + assertEquals(expected, result, true); + } + +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/HelloController.java new file mode 100644 index 000000000..ab400dfc8 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/HelloController.java @@ -0,0 +1,67 @@ +package test.org.springdoc.api.v30.app213; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.servers.Server; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * @author bnasslahsen + */ +@OpenAPIDefinition( + info = @Info( + + extensions = { @Extension( + name = "${api.extensions.name}", + properties = { + @ExtensionProperty(name = "type", value = "${api.extensions.properties.type}"), + @ExtensionProperty(name = "connectionId", value = "${api.extensions.properties.connectionId}"), + @ExtensionProperty(name = "httpMethod", value = "GET"), + @ExtensionProperty( + name = "uri", + value = "${api.extensions.properties.uri}/testcontroller/getTest"), + @ExtensionProperty(name = "passthroughBehavior", value = "${api.extensions.properties.passthroughBehavior}"), + @ExtensionProperty(name = "connectionType", value = "${api.extensions.properties.connectionType}") }) }, + + title = "${api.info.title}", + version = "${api.info.version}", + description = "${api.info.description}", + termsOfService = "${api.info.termsOfService}"), + servers = { @Server(description = "${api.server.description}", url = "${api.server.url}") }) +@Tag(name = "${api.tag.name}", description = "${api.tag.description}") +@RestController +@RequestMapping(value = "${api.base-request-mapping}/testcontroller", produces = MediaType.APPLICATION_JSON_VALUE) +public class HelloController { + + @PostMapping(path = "/", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE) + @Operation( + summary = "Get Test", + description = "Get Test", + extensions = { @Extension( + name = "${api.extensions.name}", + properties = { + @ExtensionProperty(name = "type", value = "${api.extensions.properties.type}"), + @ExtensionProperty(name = "connectionId", value = "${api.extensions.properties.connectionId}"), + @ExtensionProperty(name = "httpMethod", value = "GET"), + @ExtensionProperty( + name = "uri", + value = "${api.extensions.properties.uri}/testcontroller/getTest"), + @ExtensionProperty(name = "passthroughBehavior", value = "${api.extensions.properties.passthroughBehavior}"), + @ExtensionProperty(name = "connectionType", value = "${api.extensions.properties.connectionType}") }) }) + + public PersonDTO queryMyDto() { + // This return a PageImpl with the data, the method parameter 'query' is a pojo containg filter properties + return null; + } + +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/PersonDTO.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/PersonDTO.java new file mode 100644 index 000000000..d6a15a849 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/PersonDTO.java @@ -0,0 +1,113 @@ +/* + * + * * 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.v30.app213; + +/** + * The type Person dto. + */ +public class PersonDTO { + + /** + * The email + */ + private String email; + + /** + * The firstName + */ + private String firstName; + + /** + * The lastName + */ + private String lastName; + + /** + * Instantiates a new Person dto. + */ + public PersonDTO() { + } + + /** + * Instantiates a new Person dto. + * + * @param email the email + * @param firstName the first name + * @param lastName the last name + */ + public PersonDTO(final String email, final String firstName, final String lastName) { + this.email = email; + this.firstName = firstName; + this.lastName = lastName; + } + + /** + * Gets email. + * + * @return the email + */ + public String getEmail() { + return email; + } + + /** + * Sets email. + * + * @param email the email + */ + public void setEmail(final String email) { + this.email = email; + } + + /** + * Gets first name. + * + * @return the first name + */ + public String getFirstName() { + return firstName; + } + + /** + * Sets first name. + * + * @param firstName the first name + */ + public void setFirstName(final String firstName) { + this.firstName = firstName; + } + + /** + * Gets last name. + * + * @return the last name + */ + public String getLastName() { + return lastName; + } + + /** + * Sets last name. + * + * @param lastName the last name + */ + public void setLastName(final String lastName) { + this.lastName = lastName; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/SpringDocApp213Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/SpringDocApp213Test.java new file mode 100644 index 000000000..48a5a17e8 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app213/SpringDocApp213Test.java @@ -0,0 +1,39 @@ +/* + * + * * Copyright 2019-2023 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.v30.app213; + +import org.springdoc.core.customizers.SpecPropertiesCustomizer; +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + *

+ * A test for {@link SpecPropertiesCustomizer} + */ +@SpringBootTest +@ActiveProfiles("213") +public class SpringDocApp213Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215Test.java new file mode 100644 index 000000000..1e7ad87ec --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215Test.java @@ -0,0 +1,44 @@ +/* + * + * * 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.v30.app215; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.Constants; +import test.org.springdoc.api.AbstractCommonTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.ComponentScan; + +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(properties = SPRINGDOC_ENABLE_DEFAULT_API_DOCS+"=false") +public class SpringDocApp215Test extends AbstractCommonTest { + + @SpringBootApplication + @ComponentScan(basePackages = { "org.springdoc" }) + static class SpringDocTestApp {} + + @Test + public void test_disable_default_api_docs() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)).andExpect(status().isNotFound()); + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215bisTest.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215bisTest.java new file mode 100644 index 000000000..2f4773a32 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app215/SpringDocApp215bisTest.java @@ -0,0 +1,44 @@ +/* + * + * * 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.v30.app215; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.Constants; +import test.org.springdoc.api.AbstractCommonTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.ComponentScan; + +import static org.springdoc.core.Constants.SPRINGDOC_ENABLE_DEFAULT_API_DOCS; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(properties = SPRINGDOC_ENABLE_DEFAULT_API_DOCS+"=true") +public class SpringDocApp215bisTest extends AbstractCommonTest { + + @SpringBootApplication + @ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.v30.app214" }) + static class SpringDocTestApp {} + + @Test + public void test_enable_default_api_docs() throws Exception { + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)).andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/HelloController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/HelloController.java new file mode 100644 index 000000000..e3ea9f4c3 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/HelloController.java @@ -0,0 +1,47 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2022 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.v30.app216; + + + +import io.swagger.v3.oas.annotations.Operation; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +public class HelloController { + + @Operation(description = "a") + @GetMapping(path = "/", produces = "a/a") + public String a() { + return "A"; + } + + @Operation(description = "b") + @GetMapping(path = "/", produces = "b/b") + public String b() { + return "B"; + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/SpringDocApp216Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/SpringDocApp216Test.java new file mode 100644 index 000000000..ed95145a3 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v30/app216/SpringDocApp216Test.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2019-2023 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.v30.app216; + +import org.springdoc.core.customizers.SpecPropertiesCustomizer; +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; + +/** + *

+ * A test for {@link SpecPropertiesCustomizer} + */ +@SpringBootTest +public class SpringDocApp216Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesController.java new file mode 100644 index 000000000..ad95dc506 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesController.java @@ -0,0 +1,35 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2023 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.v31.app7; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ExamplesController { + + @GetMapping(value = "/") + public ExamplesResponse index() { + return null; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesResponse.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesResponse.java new file mode 100644 index 000000000..e74c097de --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/ExamplesResponse.java @@ -0,0 +1,29 @@ +package test.org.springdoc.api.v31.app7; + +import io.swagger.v3.oas.annotations.media.Schema; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +public class ExamplesResponse{ + @Schema(description = "name", requiredMode = REQUIRED, examples = { "name" }) + String name; + @Schema(description = "subject", requiredMode = REQUIRED, example = "Hello", examples = { "Hello", "World" }) + String subject; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } +} + diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/SpringDocApp7Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/SpringDocApp7Test.java new file mode 100644 index 000000000..22d05ba05 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app7/SpringDocApp7Test.java @@ -0,0 +1,35 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2023 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.v31.app7; + +import test.org.springdoc.api.v31.AbstractSpringDocV31Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +public class SpringDocApp7Test extends AbstractSpringDocV31Test { + + @SpringBootApplication + static class SpringDocTestApp { + + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesController.java new file mode 100644 index 000000000..7809995fd --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesController.java @@ -0,0 +1,36 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2023 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.v31.app8; + + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ExamplesController { + + @GetMapping(value = "/") + public ExamplesResponse index() { + return null; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesResponse.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesResponse.java new file mode 100644 index 000000000..bcbe001dc --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/ExamplesResponse.java @@ -0,0 +1,49 @@ +package test.org.springdoc.api.v31.app8; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public class ExamplesResponse { + + @Schema(description = "self's user info", requiredMode = RequiredMode.REQUIRED) + private UserInfo self; + + @Schema(description = "friend, deprecated, use friends instead", requiredMode = RequiredMode.NOT_REQUIRED) + @Deprecated + private UserInfo friend; + + @Schema(description = "friends", requiredMode = RequiredMode.NOT_REQUIRED) + private List friends; + + public ExamplesResponse(UserInfo self, UserInfo friend, List friends) { + this.self = self; + this.friend = friend; + this.friends = friends; + } + + public UserInfo getSelf() { + return self; + } + + public void setSelf(UserInfo self) { + this.self = self; + } + + public UserInfo getFriend() { + return friend; + } + + public void setFriend(UserInfo friend) { + this.friend = friend; + } + + public List getFriends() { + return friends; + } + + public void setFriends(List friends) { + this.friends = friends; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/FooBar.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/FooBar.java new file mode 100644 index 000000000..6d2f7b10a --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/FooBar.java @@ -0,0 +1,32 @@ +package test.org.springdoc.api.v31.app8; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "the foo bar", deprecated = true) +public class FooBar { + private int foo; + + public FooBar(int foo) { + this.foo = foo; + } + + @Schema(description = "foo", required = true, examples = {"1", "2"}) + public int getFoo() { + return foo; + } + + public void setFoo(int foo) { + this.foo = foo; + } + + public static void main(String[] args) { + FooBar fooBar = new FooBar(1); + System.out.println("Foo: " + fooBar.getFoo()); + } +} + + + + + + diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/SpringDocApp8Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/SpringDocApp8Test.java new file mode 100644 index 000000000..5864b1d8c --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/SpringDocApp8Test.java @@ -0,0 +1,35 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2023 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.v31.app8; + +import test.org.springdoc.api.v31.AbstractSpringDocV31Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +public class SpringDocApp8Test extends AbstractSpringDocV31Test { + + @SpringBootApplication + static class SpringDocTestApp { + + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/UserInfo.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/UserInfo.java new file mode 100644 index 000000000..74904a83c --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/v31/app8/UserInfo.java @@ -0,0 +1,45 @@ +package test.org.springdoc.api.v31.app8; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "user info") +public class UserInfo { + @Schema(description = "The user's name", required = true, examples = {"Madoka", "Homura"}) + private String name; + + @Schema(description = "The user's age", required = true, examples = {"114", "514"}) + private int age; + + @Schema(description = "The user's fooBar", required = true) + private FooBar fooBar; + + public UserInfo(String name, int age, FooBar fooBar) { + this.name = name; + this.age = age; + this.fooBar = fooBar; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public FooBar getFooBar() { + return fooBar; + } + + public void setFooBar(FooBar fooBar) { + this.fooBar = fooBar; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/application-212.yml b/springdoc-openapi-webmvc-core/src/test/resources/application-212.yml new file mode 100644 index 000000000..37f4119be --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/application-212.yml @@ -0,0 +1,35 @@ +springdoc: + spec-properties: + info: + title: Api info title + description: Api info description + version: Api info version + components: + schemas: + PersonDTO: + description: Description for PersonDTO component + properties: + name: + description: Description for 'name' property + example: Example value for 'name' property + paths: + persons: + description: Description of operationId 'persons' + summary: Summary of operationId 'persons' + apiGroupName: + info: + title: ApiGroupName info title + description: ApiGroupName info description + version: ApiGroupName info version + components: + schemas: + PersonDTO: + description: Description for PersonDTO component in ApiGroupName + properties: + name: + description: Description for 'name' property in ApiGroupName + example: Example value for 'name' property in ApiGroupName + paths: + persons: + description: Description of operationId 'persons' in ApiGroupName + summary: Summary of operationId 'persons' in ApiGroupName \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/application-213.yml b/springdoc-openapi-webmvc-core/src/test/resources/application-213.yml new file mode 100644 index 000000000..475dbd244 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/application-213.yml @@ -0,0 +1,26 @@ +api: + base-request-mapping: "" + info: + title: MKMTestWS (Test Microservice API) + version: "1.0" + description: "This API exposes endpoints for testing anything" + termsOfService: "https://www.test.ca/terms" + server: + description: "API Server" + url: "http://localhost:8080" + tag: + name: "Test Rest API" + description: "Test Rest API" + extensions: + name: 'x-amazon-apigateway-integration' + properties: + type: "http_proxy" + connectionId: "1rqafw" + uri: "http://my-vpc/nlb-link" + passthroughBehavior: "when_no_match" + connectionType: "VPC_LINK" +springdoc: + api-docs: + resolve-extensions-properties: true + cache: + disabled: true diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app173.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app173.json index 616425fa4..b4b0ec26b 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app173.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app173.json @@ -1,8 +1,7 @@ { "openapi": "3.0.1", "info": { - "title": "OpenAPI definition", - "version": "v0" + "TEST": "HELLO" }, "servers": [ { diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app206.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app206.json new file mode 100644 index 000000000..c6f7527e5 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app206.json @@ -0,0 +1,113 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "required-mode-controller" + ], + "operationId": "index", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "firstName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "lastName", + "in": "query", + "required": true, + "schema": { + "maxLength": 10, + "minLength": 0, + "type": "string" + } + }, + { + "name": "email", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "age", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/PersonResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PersonResponse": { + "required": [ + "age", + "email", + "firstName", + "lastName" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "maxLength": 10, + "minLength": 0, + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + } + } + } + } + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app207.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app207.json new file mode 100644 index 000000000..d3750a53e --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app207.json @@ -0,0 +1,109 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/api/sysUser/page/{current}/{size}": { + "get": { + "tags": [ + "sys-user-controller" + ], + "operationId": "findPage", + "parameters": [ + { + "name": "current", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "size", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "params", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/SysUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SuperEntityObject" + } + } + } + } + } + } + } + }, + "/api/sysUser/page": { + "get": { + "tags": [ + "sys-user-controller" + ], + "operationId": "findPage_1", + "parameters": [ + { + "name": "params", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/SysUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SuperEntityObject" + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SysUser": { + "type": "object" + }, + "SuperEntityObject": { + "type": "object" + } + } + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app208.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app208.json new file mode 100644 index 000000000..b6d21887d --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app208.json @@ -0,0 +1,96 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/test1": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "test1", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "tags": [ + "hello-controller" + ], + "operationId": "test2", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestObject" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "RequestObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app209.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app209.json new file mode 100644 index 000000000..52c543602 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app209.json @@ -0,0 +1,203 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/persons3": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons3", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "persons name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons5": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons5", + "parameters": [ + { + "name": "age", + "in": "query", + "required": true, + "schema": { + "maximum": 0, + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons4": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons4", + "parameters": [ + { + "name": "age", + "in": "query", + "required": true, + "schema": { + "minimum": 0, + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons2": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons2", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "persons name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons6": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons6", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "persons name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "persons", + "parameters": [ + { + "name": "name", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": {} +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app211.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app211.json new file mode 100644 index 000000000..0bb57161b --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app211.json @@ -0,0 +1,79 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "index", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PagedObject" + } + }, + "paging": { + "$ref": "#/components/schemas/Paging" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PagedObject": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Paging": { + "type": "object", + "properties": { + "page": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "lastPage": { + "type": "integer", + "format": "int32" + } + } + } + } + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212-grouped.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212-grouped.json new file mode 100644 index 000000000..72ac0c1f1 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212-grouped.json @@ -0,0 +1,53 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "ApiGroupName info title", + "description": "ApiGroupName info description", + "version": "ApiGroupName info version" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "summary": "Summary of operationId 'persons' in ApiGroupName", + "description": "Description of operationId 'persons' in ApiGroupName", + "operationId": "persons", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/PersonDTO" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PersonDTO": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Description for 'name' property in ApiGroupName", + "example": "Example value for 'name' property in ApiGroupName" + } + }, + "description": "Description for PersonDTO component in ApiGroupName" + } + } + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212.json new file mode 100644 index 000000000..4576d3406 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app212.json @@ -0,0 +1,54 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api info title", + "description": "Api info description", + "version": "Api info version" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/persons": { + "get": { + "tags": [ + "hello-controller" + ], + "summary": "Summary of operationId 'persons'", + "description": "Description of operationId 'persons'", + "operationId": "persons", + "responses": { + "200": { + "description":"OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/PersonDTO" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PersonDTO": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Description for 'name' property", + "example": "Example value for 'name' property" + } + }, + "description": "Description for PersonDTO component" + } + } + } +} + diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app213.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app213.json new file mode 100644 index 000000000..3b4bda9d9 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app213.json @@ -0,0 +1,79 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "MKMTestWS (Test Microservice API)", + "description": "This API exposes endpoints for testing anything", + "termsOfService": "https://www.test.ca/terms", + "version": "1.0", + "x-x-amazon-apigateway-integration": { + "passthroughBehavior": "when_no_match", + "connectionId": "1rqafw", + "type": "http_proxy", + "httpMethod": "GET", + "uri": "http://my-vpc/nlb-link/testcontroller/getTest", + "connectionType": "VPC_LINK" + } + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "API Server" + } + ], + "tags": [ + { + "name": "Test Rest API", + "description": "Test Rest API" + } + ], + "paths": { + "/testcontroller/": { + "post": { + "tags": [ + "Test Rest API" + ], + "summary": "Get Test", + "description": "Get Test", + "operationId": "queryMyDto", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PersonDTO" + } + } + } + } + }, + "x-x-amazon-apigateway-integration": { + "passthroughBehavior": "when_no_match", + "connectionId": "1rqafw", + "type": "http_proxy", + "httpMethod": "GET", + "uri": "http://my-vpc/nlb-link/testcontroller/getTest", + "connectionType": "VPC_LINK" + } + } + } + }, + "components": { + "schemas": { + "PersonDTO": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + } + } + } + } + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app216.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app216.json new file mode 100644 index 000000000..27d4c5e0c --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app216.json @@ -0,0 +1,42 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "hello-controller" + ], + "description": "a", + "operationId": "b_1", + "responses": { + "200": { + "description": "OK", + "content": { + "b/b": { + "schema": { + "type": "string" + } + }, + "a/a": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": {} +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app96.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app96.json index c8b808174..b1db8dcb3 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app96.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.0.1/app96.json @@ -17,16 +17,16 @@ "hello-controller" ], "operationId": "test3", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "string" - } + "parameters": [ + { + "name": "test", + "in": "query", + "required": true, + "schema": { + "type": "string" } - }, - "required": true - }, + } + ], "responses": { "200": { "description": "OK", @@ -41,19 +41,17 @@ } } }, - "/api1": { + "/api2": { "post": { "tags": [ "hello-controller" ], - "operationId": "test1", + "operationId": "test2", "requestBody": { "content": { "application/json": { "schema": { - "minimum": 2, - "type": "integer", - "format": "int32" + "type": "string" } } }, @@ -73,17 +71,19 @@ } } }, - "/api2": { + "/api1": { "post": { "tags": [ "hello-controller" ], - "operationId": "test2", + "operationId": "test1", "requestBody": { "content": { "application/json": { "schema": { - "type": "string" + "minimum": 2, + "type": "integer", + "format": "int32" } } }, diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app1.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app1.json index e6bd12a39..67f595272 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app1.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app1.json @@ -236,7 +236,6 @@ "required": false, "explode": false, "schema": { - "type": "array", "items": {} } } @@ -454,7 +453,6 @@ "content": { "application/json": { "schema": { - "type": "array", "items": { "$ref": "#/components/schemas/PersonDTO" } diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app2.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app2.json index 12e379920..449202e26 100644 --- a/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app2.json +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app2.json @@ -901,7 +901,6 @@ "content": { "application/json": { "schema": { - "type": "array", "items": {} } } @@ -954,7 +953,6 @@ "content": { "application/xml": { "schema": { - "type": "array", "items": { "$ref": "#/components/schemas/Pet" } @@ -962,7 +960,6 @@ }, "application/json": { "schema": { - "type": "array", "items": { "$ref": "#/components/schemas/Pet" } @@ -1020,7 +1017,6 @@ "content": { "application/xml": { "schema": { - "type": "array", "items": { "$ref": "#/components/schemas/Pet" } @@ -1028,7 +1024,6 @@ }, "application/json": { "schema": { - "type": "array", "items": { "$ref": "#/components/schemas/Pet" } @@ -1132,17 +1127,6 @@ "photoUrls" ] }, - "Tag": { - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - } - }, "Order": { "properties": { "complete": { diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app7.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app7.json new file mode 100644 index 000000000..60bf98686 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app7.json @@ -0,0 +1,63 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "examples-controller" + ], + "operationId": "index", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ExamplesResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ExamplesResponse": { + "properties": { + "name": { + "type": "string", + "description": "name", + "examples": [ + "name" + ] + }, + "subject": { + "type": "string", + "description": "subject", + "example":"Hello", + "examples": [ + "Hello", + "World" + ] + } + }, + "required": [ + "name", + "subject" + ] + } + } + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app8.json b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app8.json new file mode 100644 index 000000000..8bc72ac52 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/3.1.0/app8.json @@ -0,0 +1,111 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "examples-controller" + ], + "operationId": "index", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ExamplesResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ExamplesResponse": { + "properties": { + "self": { + "$ref": "#/components/schemas/UserInfo", + "description": "self's user info" + }, + "friend": { + "$ref": "#/components/schemas/UserInfo", + "deprecated": true, + "description": "friend, deprecated, use friends instead" + }, + "friends": { + "type": "array", + "description": "friends", + "items": { + "$ref": "#/components/schemas/UserInfo" + } + } + }, + "required": [ + "self" + ] + }, + "FooBar": { + "deprecated": true, + "description": "the foo bar", + "properties": { + "foo": { + "type": "integer", + "format": "int32", + "description": "foo", + "examples": [ + "1", + "2" + ] + } + }, + "required": [ + "foo" + ] + }, + "UserInfo": { + "description": "user info", + "properties": { + "name": { + "type": "string", + "description": "The user's name", + "examples": [ + "Madoka", + "Homura" + ] + }, + "age": { + "type": "integer", + "format": "int32", + "description": "The user's age", + "examples": [ + "114", + "514" + ] + }, + "fooBar": { + "$ref": "#/components/schemas/FooBar", + "description": "The user's fooBar" + } + }, + "required": [ + "age", + "fooBar", + "name" + ] + } + } + } +}