From 697a65a005b8e52c392e612cb995db2299a55779 Mon Sep 17 00:00:00 2001 From: Patrick Boos Date: Fri, 17 Nov 2023 16:35:32 +0100 Subject: [PATCH] removed: spring boot 2.7 support --- README.md | 11 -- .../build.gradle | 62 ------- .../example/ExampleWebApplication.java | 13 -- .../configuration/ExampleConfiguration.java | 21 --- .../example/controller/MainController.java | 26 --- .../example/error/RestExceptionHandler.java | 31 ---- .../src/main/resources/application.properties | 6 - .../src/main/resources/openapi.yaml | 1 - .../build.gradle | 60 ------- .../example/ExampleWebApplication.java | 13 -- .../configuration/ExampleConfiguration.java | 15 -- .../example/controller/MainController.java | 30 ---- .../error/GlobalErrorWebExceptionHandler.java | 57 ------ .../src/main/resources/application.properties | 8 - .../src/main/resources/openapi.yaml | 1 - gradle.properties | 1 - settings.gradle | 7 - .../build.gradle | 30 ---- .../SpringWebLibraryAutoConfiguration.java | 41 ----- .../factory/ContentCachingWrapperFactory.java | 16 -- .../factory/ServletMetaDataFactory.java | 41 ----- .../filter/OpenApiValidationHttpFilter.java | 153 ---------------- ...ot.autoconfigure.AutoConfiguration.imports | 1 - ...toConfigurationApplicationContextTest.java | 73 -------- .../filter/OpenApiValidationHttpFilter.java | 5 + .../build.gradle | 26 --- ...SpringWebFluxLibraryAutoConfiguration.java | 40 ----- .../factory/ReactiveMetaDataFactory.java | 36 ---- .../filter/OpenApiValidationWebFilter.java | 169 ------------------ ...BodyCachingServerHttpRequestDecorator.java | 55 ------ ...odyCachingServerHttpResponseDecorator.java | 56 ------ .../filter/decorator/DecoratorBuilder.java | 22 --- ...ot.autoconfigure.AutoConfiguration.imports | 1 - ...toConfigurationApplicationContextTest.java | 73 -------- 34 files changed, 5 insertions(+), 1196 deletions(-) delete mode 100644 examples/example-spring-boot-starter-web-spring2.7/build.gradle delete mode 100644 examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java delete mode 100644 examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java delete mode 100644 examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java delete mode 100644 examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/RestExceptionHandler.java delete mode 100644 examples/example-spring-boot-starter-web-spring2.7/src/main/resources/application.properties delete mode 120000 examples/example-spring-boot-starter-web-spring2.7/src/main/resources/openapi.yaml delete mode 100644 examples/example-spring-boot-starter-webflux-spring2.7/build.gradle delete mode 100644 examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java delete mode 100644 examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java delete mode 100644 examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java delete mode 100644 examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/GlobalErrorWebExceptionHandler.java delete mode 100644 examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/application.properties delete mode 120000 examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/openapi.yaml delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/build.gradle delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfiguration.java delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ContentCachingWrapperFactory.java delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ServletMetaDataFactory.java delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-boot-starter/spring-boot-starter-web-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfigurationApplicationContextTest.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/build.gradle delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfiguration.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ReactiveMetaDataFactory.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationWebFilter.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpRequestDecorator.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpResponseDecorator.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/DecoratorBuilder.java delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfigurationApplicationContextTest.java diff --git a/README.md b/README.md index e779ce9..612244f 100644 --- a/README.md +++ b/README.md @@ -30,17 +30,6 @@ dependencies { } ``` -#### Spring 2.7 -Spring 2.7.x is also supported but requires to add `-spring2.7` to the `spring-boot-starter-web` -or `spring-boot-starter-webflux` artifact name. - -Following is an example for `spring-boot-starter-web`: -```groovy -dependencies { - implementation "com.getyourguide.openapi.validation:spring-boot-starter-web-spring2.7:{latest-version}" -} -``` - ### Provide OpenAPI specification file The library will require a valid OpenAPI specification file to be present in the classpath. diff --git a/examples/example-spring-boot-starter-web-spring2.7/build.gradle b/examples/example-spring-boot-starter-web-spring2.7/build.gradle deleted file mode 100644 index db9d44d..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/build.gradle +++ /dev/null @@ -1,62 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version "$springBoot27Version" - id 'io.spring.dependency-management' version "$springDependencyManagementVersion" - id 'org.openapi.generator' version "$openApiGeneratorPluginVersion" -} - - -ext { - javaxServletApiVersion = '4.0.1' -} - -dependencies { - implementation project(':examples:examples-common') - implementation project(':spring-boot-starter:spring-boot-starter-web-spring2.7') - - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation "org.openapitools:jackson-databind-nullable:$openApiJacksonDatabindNullableVersion" - implementation "javax.servlet:javax.servlet-api:$javaxServletApiVersion" - implementation "io.swagger.core.v3:swagger-annotations:$swaggerAnnotationsVersion" -} - -test { - useJUnitPlatform() -} - -def generatedSourceDirectory = "$buildDir/generated" -sourceSets { - main { - java { - srcDir generatedSourceDirectory + '/src/main/java' - } - } -} - -openApiValidate { - inputSpec = "$projectDir/src/main/resources/openapi.yaml" - recommend = true -} - -openApiGenerate { - generatorName = "spring" - inputSpec = "$projectDir/src/main/resources/openapi.yaml" - outputDir = generatedSourceDirectory - apiPackage = "com.getyourguide.openapi.validation.example.openapi" - invokerPackage = "com.getyourguide.openapi.validation.example.openapi" - modelPackage = "com.getyourguide.openapi.validation.example.openapi.model" - configOptions = [ - useSpringBoot3 : "false", - dateLibrary : "java8", - performBeanValidation : "true", - hideGenerationTimestamp: "true", - serializableModel : "true", - interfaceOnly : "true", - skipDefaultInterface : "true", - useTags : "true" - ] -} - -tasks.openApiGenerate.dependsOn tasks.openApiValidate -tasks.compileJava.dependsOn tasks.openApiGenerate diff --git a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java b/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java deleted file mode 100644 index efeb750..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.getyourguide.openapi.validation.example; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class ExampleWebApplication { - - public static void main(String[] args) { - SpringApplication.run(ExampleWebApplication.class, args); - } - -} diff --git a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java b/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java deleted file mode 100644 index c392a61..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.getyourguide.openapi.validation.example.configuration; - -import com.getyourguide.openapi.validation.api.log.LoggerExtension; -import com.getyourguide.openapi.validation.api.metrics.client.MetricsClient; -import com.getyourguide.openapi.validation.example.logging.ExampleLoggerExtension; -import com.getyourguide.openapi.validation.metrics.client.LoggingMetricsClient; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class ExampleConfiguration { - @Bean - public MetricsClient metricsClient() { - return new LoggingMetricsClient(); - } - - @Bean - public LoggerExtension loggerExtension() { - return new ExampleLoggerExtension(); - } -} diff --git a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java b/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java deleted file mode 100644 index 38b7a82..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.getyourguide.openapi.validation.example.controller; - -import com.getyourguide.openapi.validation.example.openapi.DefaultApi; -import com.getyourguide.openapi.validation.example.openapi.model.IndexRequest; -import com.getyourguide.openapi.validation.example.openapi.model.IndexResponse; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class MainController implements DefaultApi { - @Override - public ResponseEntity deleteIndex() { - return ResponseEntity.noContent().build(); - } - - @Override - public ResponseEntity getIndex(String fromDate) { - return ResponseEntity.ok(new IndexResponse().name(null)); - } - - @Override - public ResponseEntity postIndex(IndexRequest indexRequest) { - return ResponseEntity.ok(new IndexResponse().name(indexRequest.getName())); - } - -} diff --git a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/RestExceptionHandler.java b/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/RestExceptionHandler.java deleted file mode 100644 index bcf770a..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/RestExceptionHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.getyourguide.openapi.validation.example.error; - -import com.getyourguide.openapi.validation.example.openapi.model.BadRequestResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -@ControllerAdvice -public class RestExceptionHandler extends ResponseEntityExceptionHandler { - @Override - protected ResponseEntity handleExceptionInternal(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { - if (status.value() == 400) { - return ResponseEntity.badRequest().body(new BadRequestResponse().error(ex.getMessage())); - } - if (status.value() == 500) { - return ResponseEntity.internalServerError().build(); - } - - return super.handleExceptionInternal(ex, body, headers, status, request); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleInternalServerError(Exception ex) { - return ResponseEntity.internalServerError().build(); - } -} diff --git a/examples/example-spring-boot-starter-web-spring2.7/src/main/resources/application.properties b/examples/example-spring-boot-starter-web-spring2.7/src/main/resources/application.properties deleted file mode 100644 index 6ce2dbf..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -openapi.validation.sample-rate=0.5 -openapi.validation.specification-file-path=openapi.yaml -openapi.validation.excluded-paths=/_readiness,/_liveness,/_metrics -openapi.validation.validation-report-throttle-wait-seconds=10 -openapi.validation.validation-report-metric-name=openapi_error -openapi.validation.validation-report-metric-additional-tags=service=example,team=chk diff --git a/examples/example-spring-boot-starter-web-spring2.7/src/main/resources/openapi.yaml b/examples/example-spring-boot-starter-web-spring2.7/src/main/resources/openapi.yaml deleted file mode 120000 index cbe0efc..0000000 --- a/examples/example-spring-boot-starter-web-spring2.7/src/main/resources/openapi.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../../openapi.yaml \ No newline at end of file diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/build.gradle b/examples/example-spring-boot-starter-webflux-spring2.7/build.gradle deleted file mode 100644 index ebfb92d..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version "$springBoot27Version" - id 'io.spring.dependency-management' version "$springDependencyManagementVersion" - id 'org.openapi.generator' version "$openApiGeneratorPluginVersion" -} - -dependencies { - implementation project(':examples:examples-common') - implementation project(':spring-boot-starter:spring-boot-starter-webflux-spring2.7') - implementation project(':metrics-reporter:metrics-reporter-datadog-spring-boot') - - implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation "org.openapitools:jackson-databind-nullable:$openApiJacksonDatabindNullableVersion" -// implementation "jakarta.validation:jakarta.validation-api:2.0.2" - implementation 'javax.validation:validation-api:2.0.1.Final' - implementation "io.swagger.core.v3:swagger-annotations:$swaggerAnnotationsVersion" -} - -test { - useJUnitPlatform() -} - -def generatedSourceDirectory = "$buildDir/generated" -sourceSets { - main { - java { - srcDir generatedSourceDirectory + '/src/main/java' - } - } -} - -openApiValidate { - inputSpec = "$projectDir/src/main/resources/openapi.yaml" - recommend = true -} - -openApiGenerate { - generatorName = "spring" - inputSpec = "$projectDir/src/main/resources/openapi.yaml" - outputDir = generatedSourceDirectory - apiPackage = "com.getyourguide.openapi.validation.example.openapi" - invokerPackage = "com.getyourguide.openapi.validation.example.openapi" - modelPackage = "com.getyourguide.openapi.validation.example.openapi.model" - configOptions = [ - useSpringBoot3 : "false", - reactive : "true", - dateLibrary : "java8", - performBeanValidation : "true", - hideGenerationTimestamp: "true", - serializableModel : "true", - interfaceOnly : "true", - skipDefaultInterface : "true", - useTags : "true" - ] -} - -tasks.openApiGenerate.dependsOn tasks.openApiValidate -tasks.compileJava.dependsOn tasks.openApiGenerate diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java b/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java deleted file mode 100644 index efeb750..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/ExampleWebApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.getyourguide.openapi.validation.example; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class ExampleWebApplication { - - public static void main(String[] args) { - SpringApplication.run(ExampleWebApplication.class, args); - } - -} diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java b/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java deleted file mode 100644 index f873cc2..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/configuration/ExampleConfiguration.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.getyourguide.openapi.validation.example.configuration; - -import com.getyourguide.openapi.validation.api.log.LoggerExtension; -import com.getyourguide.openapi.validation.example.logging.ExampleLoggerExtension; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class ExampleConfiguration { - - @Bean - public LoggerExtension loggerExtension() { - return new ExampleLoggerExtension(); - } -} diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java b/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java deleted file mode 100644 index 040853d..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/controller/MainController.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.getyourguide.openapi.validation.example.controller; - -import com.getyourguide.openapi.validation.example.openapi.DefaultApi; -import com.getyourguide.openapi.validation.example.openapi.model.IndexRequest; -import com.getyourguide.openapi.validation.example.openapi.model.IndexResponse; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -@RestController -public class MainController implements DefaultApi { - @Override - public Mono> deleteIndex(ServerWebExchange exchange) { - return Mono.just(ResponseEntity.noContent().build()); - } - - @Override - public Mono> getIndex(String fromDate, ServerWebExchange exchange) { - return Mono.just(new ResponseEntity<>(new IndexResponse().name(null), HttpStatus.OK)); - } - - @Override - public Mono> postIndex(Mono indexRequest, ServerWebExchange exchange) { - return indexRequest - .map(body -> new ResponseEntity<>(new IndexResponse().name(body.getName()), HttpStatus.OK)) - .switchIfEmpty(Mono.just(ResponseEntity.badRequest().build())); - } -} diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/GlobalErrorWebExceptionHandler.java b/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/GlobalErrorWebExceptionHandler.java deleted file mode 100644 index 9c94705..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/example/error/GlobalErrorWebExceptionHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.getyourguide.openapi.validation.example.error; - -import com.getyourguide.openapi.validation.example.openapi.model.BadRequestResponse; -import java.util.Optional; -import org.springframework.boot.autoconfigure.web.WebProperties; -import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler; -import org.springframework.boot.web.error.ErrorAttributeOptions; -import org.springframework.boot.web.reactive.error.ErrorAttributes; -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.server.RequestPredicates; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.RouterFunctions; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; - -@Component -@Order(-2) -public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { - public GlobalErrorWebExceptionHandler( - ErrorAttributes errorAttributes, - WebProperties webProperties, - ApplicationContext applicationContext, - ServerCodecConfigurer serverCodecConfigurer - ) { - super(errorAttributes, webProperties.getResources(), applicationContext); - super.setMessageWriters(serverCodecConfigurer.getWriters()); - super.setMessageReaders(serverCodecConfigurer.getReaders()); - } - - @Override - protected RouterFunction getRoutingFunction(ErrorAttributes errorAttributes) { - return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); - } - - private Mono renderErrorResponse(ServerRequest request) { - var errorAttributesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults()); - int status = (int) Optional.ofNullable(errorAttributesMap.get("status")).orElse(500); - var error = getError(request); - - if (status == 400) { - var responseBody = new BadRequestResponse().error(error.getMessage()); - return ServerResponse - .status(status) - .contentType(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue(responseBody)); - } - - return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } -} diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/application.properties b/examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/application.properties deleted file mode 100644 index e47563c..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/application.properties +++ /dev/null @@ -1,8 +0,0 @@ -openapi.validation.sample-rate=0.5 -openapi.validation.specification-file-path=openapi.yaml -openapi.validation.excluded-paths=/_readiness,/_liveness,/_metrics -openapi.validation.validation-report-throttle-wait-seconds=10 -openapi.validation.validation-report-metric-name=openapi_error -openapi.validation.validation-report-metric-additional-tags=service=example,team=chk -openapi.validation.datadog.statsd.service.host=localhost -openapi.validation.datadog.statsd.service.port=7992 diff --git a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/openapi.yaml b/examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/openapi.yaml deleted file mode 120000 index cbe0efc..0000000 --- a/examples/example-spring-boot-starter-webflux-spring2.7/src/main/resources/openapi.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../../openapi.yaml \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 74de288..c1cadab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ # Since we include webmvc project here, we should keep the versions in sync with it springBootVersion=3.0.2 -springBoot27Version=2.7.8 springDependencyManagementVersion=1.1.0 # This should ideally be in sync with the version provided by DatadogPlugin diff --git a/settings.gradle b/settings.gradle index b9d7a42..208f66a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,10 +13,3 @@ include(':metrics-reporter:metrics-reporter-datadog-spring-boot') include(':examples:examples-common') include(':examples:example-spring-boot-starter-web') include(':examples:example-spring-boot-starter-webflux') - - -// Spring 2.7 support -include(':spring-boot-starter:spring-boot-starter-web-spring2.7') -include(':spring-boot-starter:spring-boot-starter-webflux-spring2.7') -include(':examples:example-spring-boot-starter-web-spring2.7') -include(':examples:example-spring-boot-starter-webflux-spring2.7') diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/build.gradle b/spring-boot-starter/spring-boot-starter-web-spring2.7/build.gradle deleted file mode 100644 index 89041d9..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/build.gradle +++ /dev/null @@ -1,30 +0,0 @@ -import org.springframework.boot.gradle.plugin.SpringBootPlugin - -plugins { - id 'org.springframework.boot' version "$springBoot27Version" apply false -} - -ext { - javaxServletApiVersion = '4.0.1' -} - -apply from: "${rootDir}/gradle/publish-module.gradle" - -dependencies { - implementation platform(SpringBootPlugin.BOM_COORDINATES) - - api project(':openapi-validation-api') - implementation project(':openapi-validation-core') - api project(':spring-boot-starter:spring-boot-starter-core') - - compileOnly 'org.springframework.boot:spring-boot-starter-web' - - implementation 'org.springframework.boot:spring-boot-autoconfigure' - - // TODO use spotbugs instead and also apply to all modules? - implementation "com.google.code.findbugs:jsr305:$findBugsVersion" - - testImplementation "org.springframework.boot:spring-boot-starter-test" - testImplementation 'org.springframework:spring-web' - testImplementation "javax.servlet:javax.servlet-api:$javaxServletApiVersion" // For javax.servlet.ServletContext -} diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfiguration.java b/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfiguration.java deleted file mode 100644 index 8361d9d..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.getyourguide.openapi.validation.autoconfigure; - -import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; - -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import com.getyourguide.openapi.validation.core.OpenApiRequestValidator; -import com.getyourguide.openapi.validation.factory.ContentCachingWrapperFactory; -import com.getyourguide.openapi.validation.factory.ServletMetaDataFactory; -import com.getyourguide.openapi.validation.filter.OpenApiValidationHttpFilter; -import lombok.AllArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@AllArgsConstructor -public class SpringWebLibraryAutoConfiguration { - - @Bean - @ConditionalOnWebApplication(type = Type.SERVLET) - public ServletMetaDataFactory servletMetaDataFactory() { - return new ServletMetaDataFactory(); - } - - @Bean - @ConditionalOnWebApplication(type = Type.SERVLET) - public ContentCachingWrapperFactory contentCachingWrapperFactory() { - return new ContentCachingWrapperFactory(); - } - - @Bean - @ConditionalOnWebApplication(type = Type.SERVLET) - public OpenApiValidationHttpFilter openApiValidationHttpFilter( - OpenApiRequestValidator validator, - TrafficSelector trafficSelector, - ServletMetaDataFactory metaDataFactory, - ContentCachingWrapperFactory contentCachingWrapperFactory - ) { - return new OpenApiValidationHttpFilter(validator, trafficSelector, metaDataFactory, contentCachingWrapperFactory); - } -} diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ContentCachingWrapperFactory.java b/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ContentCachingWrapperFactory.java deleted file mode 100644 index 6474efd..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ContentCachingWrapperFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.getyourguide.openapi.validation.factory; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.springframework.web.util.ContentCachingRequestWrapper; -import org.springframework.web.util.ContentCachingResponseWrapper; - -public class ContentCachingWrapperFactory { - public ContentCachingRequestWrapper buildContentCachingRequestWrapper(HttpServletRequest request) { - return new ContentCachingRequestWrapper(request); - } - - public ContentCachingResponseWrapper buildContentCachingResponseWrapper(HttpServletResponse response) { - return new ContentCachingResponseWrapper(response); - } -} diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ServletMetaDataFactory.java b/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ServletMetaDataFactory.java deleted file mode 100644 index 707d830..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ServletMetaDataFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.getyourguide.openapi.validation.factory; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.model.ResponseMetaData; -import java.util.TreeMap; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -public class ServletMetaDataFactory { - - public static final String HEADER_CONTENT_TYPE = "Content-Type"; - - public RequestMetaData buildRequestMetaData(HttpServletRequest request) { - var uri = ServletUriComponentsBuilder.fromRequest(request).build().toUri(); - return new RequestMetaData(request.getMethod(), uri, getHeaders(request)); - } - - public ResponseMetaData buildResponseMetaData(HttpServletResponse response) { - return new ResponseMetaData(response.getStatus(), response.getContentType(), getHeaders(response)); - } - - private static TreeMap getHeaders(HttpServletRequest request) { - var headers = new TreeMap(String.CASE_INSENSITIVE_ORDER); - var headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - var headerName = headerNames.nextElement(); - headers.put(headerName, request.getHeader(headerName)); - } - return headers; - } - - private static TreeMap getHeaders(HttpServletResponse response) { - var headers = new TreeMap(String.CASE_INSENSITIVE_ORDER); - for (var headerName : response.getHeaderNames()) { - headers.put(headerName, response.getHeader(headerName)); - } - headers.put(HEADER_CONTENT_TYPE, response.getContentType()); // This one is not yet in the headers - return headers; - } -} diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java b/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java deleted file mode 100644 index 1f6f9eb..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.getyourguide.openapi.validation.filter; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.model.ResponseMetaData; -import com.getyourguide.openapi.validation.api.model.ValidationResult; -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import com.getyourguide.openapi.validation.core.OpenApiRequestValidator; -import com.getyourguide.openapi.validation.factory.ContentCachingWrapperFactory; -import com.getyourguide.openapi.validation.factory.ServletMetaDataFactory; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import javax.annotation.Nullable; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.util.ContentCachingRequestWrapper; -import org.springframework.web.util.ContentCachingResponseWrapper; - -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -@Slf4j -@AllArgsConstructor -public class OpenApiValidationHttpFilter extends HttpFilter { - - private final OpenApiRequestValidator validator; - private final TrafficSelector trafficSelector; - private final ServletMetaDataFactory metaDataFactory; - private final ContentCachingWrapperFactory contentCachingWrapperFactory; - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { - super.doFilter(request, response, chain); - return; - } - - var httpServletRequest = (HttpServletRequest) request; - var httpServletResponse = (HttpServletResponse) response; - var requestMetaData = metaDataFactory.buildRequestMetaData(httpServletRequest); - if (!validator.isReady() || !trafficSelector.shouldRequestBeValidated(requestMetaData)) { - super.doFilter(request, response, chain); - return; - } - - var requestWrapper = contentCachingWrapperFactory.buildContentCachingRequestWrapper(httpServletRequest); - var responseWrapper = contentCachingWrapperFactory.buildContentCachingResponseWrapper(httpServletResponse); - - var alreadyDidRequestValidation = validateRequestWithFailOnViolation(requestWrapper, requestMetaData); - try { - super.doFilter(requestWrapper, responseWrapper, chain); - } finally { - var responseMetaData = metaDataFactory.buildResponseMetaData(responseWrapper); - if (!alreadyDidRequestValidation) { - validateRequest(requestWrapper, requestMetaData, responseMetaData, RunType.ASYNC); - } - - var validateResponseResult = validateResponse( - responseWrapper, - requestMetaData, - responseMetaData, - getRunTypeForResponseValidation(requestMetaData) - ); - throwStatusExceptionOnViolation(validateResponseResult, "Response validation failed"); - - responseWrapper.copyBodyToResponse(); // Needs to be done on every call, otherwise there won't be a response body - } - } - - private RunType getRunTypeForResponseValidation(RequestMetaData requestMetaData) { - if (trafficSelector.shouldFailOnResponseViolation(requestMetaData)) { - return RunType.SYNC; - } else { - return RunType.ASYNC; - } - } - - private boolean validateRequestWithFailOnViolation( - ContentCachingRequestWrapper request, - RequestMetaData requestMetaData - ) { - if (!trafficSelector.shouldFailOnRequestViolation(requestMetaData)) { - return false; - } - - var validateRequestResult = validateRequest(request, requestMetaData, null, RunType.SYNC); - throwStatusExceptionOnViolation(validateRequestResult, "Request validation failed"); - return true; - } - - private ValidationResult validateRequest( - ContentCachingRequestWrapper request, - RequestMetaData requestMetaData, - @Nullable ResponseMetaData responseMetaData, - RunType runType - ) { - if (!trafficSelector.canRequestBeValidated(requestMetaData)) { - return ValidationResult.NOT_APPLICABLE; - } - - var requestBody = request.getContentType() != null - ? new String(request.getContentAsByteArray(), StandardCharsets.UTF_8) - : null; - - if (runType == RunType.ASYNC) { - validator.validateRequestObjectAsync(requestMetaData, responseMetaData, requestBody); - return ValidationResult.NOT_APPLICABLE; - } else { - return validator.validateRequestObject(requestMetaData, requestBody); - } - } - - private ValidationResult validateResponse( - ContentCachingResponseWrapper response, - RequestMetaData requestMetaData, - ResponseMetaData responseMetaData, - RunType runType - ) { - if (!trafficSelector.canResponseBeValidated(requestMetaData, responseMetaData)) { - return ValidationResult.NOT_APPLICABLE; - } - - var responseBody = response.getContentType() != null - ? new String(response.getContentAsByteArray(), StandardCharsets.UTF_8) - : null; - - if (runType == RunType.ASYNC) { - validator.validateResponseObjectAsync(requestMetaData, responseMetaData, responseBody); - return ValidationResult.NOT_APPLICABLE; - } else { - return validator.validateResponseObject(requestMetaData, responseMetaData, responseBody); - } - } - - private void throwStatusExceptionOnViolation(ValidationResult validateRequestResult, String message) { - if (validateRequestResult == ValidationResult.INVALID) { - throw new ResponseStatusException(HttpStatus.valueOf(400), message); - } - } - - private enum RunType { SYNC, ASYNC } -} diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 7f42f38..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.getyourguide.openapi.validation.autoconfigure.SpringWebLibraryAutoConfiguration diff --git a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfigurationApplicationContextTest.java b/spring-boot-starter/spring-boot-starter-web-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfigurationApplicationContextTest.java deleted file mode 100644 index d0b35ea..0000000 --- a/spring-boot-starter/spring-boot-starter-web-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfigurationApplicationContextTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.getyourguide.openapi.validation.autoconfigure; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.getyourguide.openapi.validation.filter.OpenApiValidationHttpFilter; -import java.util.Optional; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.mock.web.MockServletContext; - -class SpringWebLibraryAutoConfigurationApplicationContextTest { - - private ConfigurableApplicationContext context; - - @AfterEach - void tearDown() { - Optional.ofNullable(context) - .ifPresent(ConfigurableApplicationContext::close); - } - - @Test - void webApplicationWithServletContext() { - context = servletWebApplicationContext(); - - assertThat(context.getBeansOfType(OpenApiValidationHttpFilter.class)).size().isEqualTo(1); - } - - @Test - void webApplicationWithReactiveContext() { - context = reactiveWebApplicationContext(); - - assertThat(context.getBeansOfType(OpenApiValidationHttpFilter.class)).size().isEqualTo(0); - } - - @Test - void nonWebApplicationContextShouldHaveNoFilterBeans() { - context = nonWebApplicationContext(); - - assertThat(context.getBeansOfType(OpenApiValidationHttpFilter.class)).size().isEqualTo(0); - } - - private AnnotationConfigServletWebApplicationContext servletWebApplicationContext() { - var servletContext = new AnnotationConfigServletWebApplicationContext(); - - servletContext.register(SpringWebLibraryAutoConfiguration.class, LibraryAutoConfiguration.class, FallbackLibraryAutoConfiguration.class); - servletContext.setServletContext(new MockServletContext()); - servletContext.refresh(); - - return servletContext; - } - - private AnnotationConfigReactiveWebApplicationContext reactiveWebApplicationContext() { - var reactiveContext = new AnnotationConfigReactiveWebApplicationContext(); - - reactiveContext.register(SpringWebLibraryAutoConfiguration.class, LibraryAutoConfiguration.class, FallbackLibraryAutoConfiguration.class); - reactiveContext.refresh(); - - return reactiveContext; - } - - private AnnotationConfigApplicationContext nonWebApplicationContext() { - var reactiveContext = new AnnotationConfigApplicationContext(); - - reactiveContext.register(SpringWebLibraryAutoConfiguration.class, LibraryAutoConfiguration.class, FallbackLibraryAutoConfiguration.class); - reactiveContext.refresh(); - - return reactiveContext; - } -} diff --git a/spring-boot-starter/spring-boot-starter-web/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java b/spring-boot-starter/spring-boot-starter-web/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java index c3aacea..7cec46e 100644 --- a/spring-boot-starter/spring-boot-starter-web/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java +++ b/spring-boot-starter/spring-boot-starter-web/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationHttpFilter.java @@ -61,6 +61,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha try { super.doFilter(requestWrapper, responseWrapper, chain); } finally { + // TODO problem here is that if there is something thrown that is not handled in an ErrorHandler, + // the request/response object that gets updated by the general handler is not the same object, + // therefore the actual response is not visible here. + // - Maybe we just need to remove the finally. But then we won't validate anymore on exceptions thrown. + // - Or we say optionally here that it should ignore `validation.response.body.missing` var responseMetaData = metaDataFactory.buildResponseMetaData(responseWrapper); if (!alreadyDidRequestValidation) { validateRequest(requestWrapper, requestMetaData, responseMetaData, RunType.ASYNC); diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/build.gradle b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/build.gradle deleted file mode 100644 index bb58203..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -import org.springframework.boot.gradle.plugin.SpringBootPlugin - -plugins { - id 'org.springframework.boot' version "$springBoot27Version" apply false -} - -apply from: "${rootDir}/gradle/publish-module.gradle" - -dependencies { - implementation platform(SpringBootPlugin.BOM_COORDINATES) - - api project(':openapi-validation-api') - implementation project(':openapi-validation-core') - api project(':spring-boot-starter:spring-boot-starter-core') - - compileOnly 'org.springframework.boot:spring-boot-starter-webflux' - - implementation 'org.springframework.boot:spring-boot-autoconfigure' - - // TODO use spotbugs instead and also apply to all modules? - implementation "com.google.code.findbugs:jsr305:$findBugsVersion" - - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework:spring-webflux' - testImplementation 'org.apache.tomcat.embed:tomcat-embed-core' // For jakarta.servlet.ServletContext -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfiguration.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfiguration.java deleted file mode 100644 index 356cb1e..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfiguration.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.getyourguide.openapi.validation.autoconfigure; - -import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; - -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import com.getyourguide.openapi.validation.core.OpenApiRequestValidator; -import com.getyourguide.openapi.validation.factory.ReactiveMetaDataFactory; -import com.getyourguide.openapi.validation.filter.OpenApiValidationWebFilter; -import com.getyourguide.openapi.validation.filter.decorator.DecoratorBuilder; -import lombok.AllArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@AllArgsConstructor -public class SpringWebFluxLibraryAutoConfiguration { - @Bean - @ConditionalOnWebApplication(type = Type.REACTIVE) - public DecoratorBuilder decoratorBuilder(TrafficSelector trafficSelector, ReactiveMetaDataFactory metaDataFactory) { - return new DecoratorBuilder(trafficSelector, metaDataFactory); - } - - @Bean - @ConditionalOnWebApplication(type = Type.REACTIVE) - public ReactiveMetaDataFactory reactiveMetaDataFactory() { - return new ReactiveMetaDataFactory(); - } - - @Bean - @ConditionalOnWebApplication(type = Type.REACTIVE) - public OpenApiValidationWebFilter openApiValidationWebFilter( - OpenApiRequestValidator validator, - TrafficSelector trafficSelector, - ReactiveMetaDataFactory metaDataFactory, - DecoratorBuilder decoratorBuilder - ) { - return new OpenApiValidationWebFilter(validator, trafficSelector, metaDataFactory, decoratorBuilder); - } -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ReactiveMetaDataFactory.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ReactiveMetaDataFactory.java deleted file mode 100644 index e560972..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/factory/ReactiveMetaDataFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.getyourguide.openapi.validation.factory; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.model.ResponseMetaData; -import java.util.Map; -import java.util.TreeMap; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -public class ReactiveMetaDataFactory { - - public RequestMetaData buildRequestMetaData(ServerHttpRequest request) { - return buildRequestMetaData(request, request.getHeaders()); - } - - public RequestMetaData buildRequestMetaData(ServerHttpRequest request, HttpHeaders headers) { - return new RequestMetaData(request.getMethod().name(), request.getURI(), buildCaseInsensitiveHeaders(headers)); - } - - public ResponseMetaData buildResponseMetaData(ServerHttpResponse response) { - var responseHeaders = response.getHeaders(); - var responseContentType = responseHeaders.getContentType() != null ? responseHeaders.getContentType().toString() : null; - return new ResponseMetaData( - response.getStatusCode() != null ? response.getStatusCode().value() : null, - responseContentType, - buildCaseInsensitiveHeaders(responseHeaders) - ); - } - - private Map buildCaseInsensitiveHeaders(HttpHeaders headers) { - var map = new TreeMap(String.CASE_INSENSITIVE_ORDER); - headers.forEach((key, values) -> values.stream().findFirst().ifPresent(value -> map.put(key, value))); - return map; - } -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationWebFilter.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationWebFilter.java deleted file mode 100644 index 0339bab..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/OpenApiValidationWebFilter.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.getyourguide.openapi.validation.filter; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.model.ResponseMetaData; -import com.getyourguide.openapi.validation.api.model.ValidationResult; -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import com.getyourguide.openapi.validation.core.OpenApiRequestValidator; -import com.getyourguide.openapi.validation.factory.ReactiveMetaDataFactory; -import com.getyourguide.openapi.validation.filter.decorator.BodyCachingServerHttpRequestDecorator; -import com.getyourguide.openapi.validation.filter.decorator.BodyCachingServerHttpResponseDecorator; -import com.getyourguide.openapi.validation.filter.decorator.DecoratorBuilder; -import javax.annotation.Nullable; -import lombok.AllArgsConstructor; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpStatus; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; -import reactor.core.publisher.SignalType; - -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -@AllArgsConstructor -public class OpenApiValidationWebFilter implements WebFilter { - - private final OpenApiRequestValidator validator; - private final TrafficSelector trafficSelector; - private final ReactiveMetaDataFactory metaDataFactory; - private final DecoratorBuilder decoratorBuilder; - - @Override - @NonNull - public Mono filter(ServerWebExchange exchange, @NonNull WebFilterChain chain) { - var request = exchange.getRequest(); - var requestMetaData = metaDataFactory.buildRequestMetaData(request); - - if (!validator.isReady() || !trafficSelector.shouldRequestBeValidated(requestMetaData)) { - return chain.filter(exchange); - } - - return decorateWithValidation(exchange, chain, requestMetaData); - } - - @NonNull - private Mono decorateWithValidation(ServerWebExchange exchange, WebFilterChain chain, RequestMetaData requestMetaData) { - var requestDecorated = decoratorBuilder.buildBodyCachingServerHttpRequestDecorator(exchange.getRequest(), requestMetaData); - var responseDecorated = decoratorBuilder.buildBodyCachingServerHttpResponseDecorator(exchange.getResponse(), requestMetaData); - - var serverWebExchange = exchange.mutate().request(requestDecorated).response(responseDecorated).build(); - - var alreadyDidRequestValidation = validateRequestWithFailOnViolation(requestMetaData, requestDecorated); - var alreadyDidResponseValidation = validateResponseWithFailOnViolation(requestMetaData, responseDecorated); - - return chain.filter(serverWebExchange) - .doFinally(signalType -> { - // Note: Errors are not handled here. They could be handled with SignalType.ON_ERROR, but then the response body can't be accessed. - // Reason seems to be that those don't use the decorated response that is set here, but use the previous response object. - if (signalType == SignalType.ON_COMPLETE) { - var responseMetaData = metaDataFactory.buildResponseMetaData(responseDecorated); - if (!alreadyDidRequestValidation) { - validateRequest(requestDecorated, requestMetaData, responseMetaData, RunType.ASYNC); - } - if (!alreadyDidResponseValidation) { - validateResponse( - requestMetaData, responseMetaData, responseDecorated.getCachedBody(), RunType.ASYNC); - } - } - }); - } - - /** - * Validate request and fail on violation if configured to do so. - * - * @return true if validation is done as part of this method - */ - private boolean validateRequestWithFailOnViolation( - RequestMetaData requestMetaData, - BodyCachingServerHttpRequestDecorator requestDecorated - ) { - if (!trafficSelector.shouldFailOnRequestViolation(requestMetaData)) { - return false; - } - - if (requestDecorated.getHeaders().containsKey("Content-Type") && requestDecorated.getHeaders().containsKey("Content-Length")) { - requestDecorated.setOnBodyCachedListener(() -> { - var validateRequestResult = validateRequest(requestDecorated, requestMetaData, null, RunType.SYNC); - throwStatusExceptionOnViolation(validateRequestResult, "Request validation failed"); - }); - } else { - var validateRequestResult = validateRequest(requestDecorated, requestMetaData, null, RunType.SYNC); - throwStatusExceptionOnViolation(validateRequestResult, "Request validation failed"); - } - return true; - } - - private static void throwStatusExceptionOnViolation(ValidationResult validateRequestResult, String message) { - if (validateRequestResult == ValidationResult.INVALID) { - throw new ResponseStatusException(HttpStatus.valueOf(400), message); - } - } - - /** - * Validate response and fail on violation if configured to do so. - * - * @return true if validation is done as part of this method - */ - private boolean validateResponseWithFailOnViolation( - RequestMetaData requestMetaData, - BodyCachingServerHttpResponseDecorator responseDecorated - ) { - if (!trafficSelector.shouldFailOnResponseViolation(requestMetaData)) { - return false; - } - - responseDecorated.setOnBodyCachedListener(() -> { - var validateResponseResult = validateResponse( - requestMetaData, - metaDataFactory.buildResponseMetaData(responseDecorated), - responseDecorated.getCachedBody(), - RunType.SYNC - ); - throwStatusExceptionOnViolation(validateResponseResult, "Response validation failed"); - }); - return true; - } - - private ValidationResult validateRequest( - BodyCachingServerHttpRequestDecorator request, - RequestMetaData requestMetaData, - @Nullable ResponseMetaData responseMetaData, - RunType runType - ) { - if (!trafficSelector.canRequestBeValidated(requestMetaData)) { - return ValidationResult.NOT_APPLICABLE; - } - - if (runType == RunType.SYNC) { - return validator.validateRequestObject(requestMetaData, responseMetaData, request.getCachedBody()); - } else { - validator.validateRequestObjectAsync(requestMetaData, responseMetaData, request.getCachedBody()); - return ValidationResult.NOT_APPLICABLE; - } - } - - private ValidationResult validateResponse( - RequestMetaData requestMetaData, - @Nullable ResponseMetaData responseMetaData, - @Nullable String responseBody, - RunType runType - ) { - if (!trafficSelector.canResponseBeValidated(requestMetaData, responseMetaData)) { - return ValidationResult.NOT_APPLICABLE; - } - - if (runType == RunType.SYNC) { - return validator.validateResponseObject(requestMetaData, responseMetaData, responseBody); - } else { - validator.validateResponseObjectAsync(requestMetaData, responseMetaData, responseBody); - return ValidationResult.NOT_APPLICABLE; - } - } - - private enum RunType { SYNC, ASYNC } -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpRequestDecorator.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpRequestDecorator.java deleted file mode 100644 index c883a4f..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpRequestDecorator.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.getyourguide.openapi.validation.filter.decorator; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import java.nio.charset.StandardCharsets; -import lombok.Getter; -import lombok.Setter; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequestDecorator; -import org.springframework.lang.NonNull; -import reactor.core.publisher.Flux; -import reactor.core.publisher.SignalType; - -public class BodyCachingServerHttpRequestDecorator extends ServerHttpRequestDecorator { - private final TrafficSelector trafficSelector; - private final RequestMetaData requestMetaData; - - @Setter - private Runnable onBodyCachedListener; - - @Getter - private String cachedBody; - - public BodyCachingServerHttpRequestDecorator( - ServerHttpRequest delegate, - TrafficSelector trafficSelector, - RequestMetaData requestMetaData - ) { - super(delegate); - this.trafficSelector = trafficSelector; - this.requestMetaData = requestMetaData; - } - - @Override - @NonNull - public Flux getBody() { - if (!trafficSelector.canRequestBeValidated(requestMetaData)) { - return super.getBody(); - } - - return super.getBody() - .doOnNext(dataBuffer -> { - if (cachedBody == null) { - cachedBody = ""; - } - cachedBody += dataBuffer.toString(StandardCharsets.UTF_8); - }) - .doFinally(signalType -> { - if (signalType == SignalType.ON_COMPLETE && onBodyCachedListener != null) { - onBodyCachedListener.run(); - } - }); - } -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpResponseDecorator.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpResponseDecorator.java deleted file mode 100644 index 9e48219..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/BodyCachingServerHttpResponseDecorator.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.getyourguide.openapi.validation.filter.decorator; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import com.getyourguide.openapi.validation.factory.ReactiveMetaDataFactory; -import java.nio.charset.StandardCharsets; -import lombok.Getter; -import lombok.Setter; -import org.reactivestreams.Publisher; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.lang.NonNull; -import reactor.core.publisher.Mono; - -public class BodyCachingServerHttpResponseDecorator extends ServerHttpResponseDecorator { - - private final TrafficSelector trafficSelector; - private final ReactiveMetaDataFactory metaDataFactory; - private final RequestMetaData requestMetaData; - - @Setter - private Runnable onBodyCachedListener; - - @Getter - private String cachedBody; - - public BodyCachingServerHttpResponseDecorator( - ServerHttpResponse delegate, - TrafficSelector trafficSelector, - ReactiveMetaDataFactory metaDataFactory, - RequestMetaData requestMetaData - ) { - super(delegate); - this.trafficSelector = trafficSelector; - this.metaDataFactory = metaDataFactory; - this.requestMetaData = requestMetaData; - } - - @Override - @NonNull - public Mono writeWith(@NonNull Publisher body) { - var responseMetaData = metaDataFactory.buildResponseMetaData(this); - if (!trafficSelector.canResponseBeValidated(requestMetaData, responseMetaData)) { - return super.writeWith(body); - } - - var buffer = Mono.from(body).doOnNext(dataBuffer -> { - cachedBody = dataBuffer.toString(StandardCharsets.UTF_8); - if (onBodyCachedListener != null) { - onBodyCachedListener.run(); - } - }); - return super.writeWith(buffer); - } -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/DecoratorBuilder.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/DecoratorBuilder.java deleted file mode 100644 index 24a92b0..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/java/com/getyourguide/openapi/validation/filter/decorator/DecoratorBuilder.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.getyourguide.openapi.validation.filter.decorator; - -import com.getyourguide.openapi.validation.api.model.RequestMetaData; -import com.getyourguide.openapi.validation.api.selector.TrafficSelector; -import com.getyourguide.openapi.validation.factory.ReactiveMetaDataFactory; -import lombok.AllArgsConstructor; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -@AllArgsConstructor -public class DecoratorBuilder { - private final TrafficSelector trafficSelector; - private final ReactiveMetaDataFactory metaDataFactory; - - public BodyCachingServerHttpRequestDecorator buildBodyCachingServerHttpRequestDecorator(ServerHttpRequest request, RequestMetaData requestMetaData) { - return new BodyCachingServerHttpRequestDecorator(request, trafficSelector, requestMetaData); - } - - public BodyCachingServerHttpResponseDecorator buildBodyCachingServerHttpResponseDecorator(ServerHttpResponse response, RequestMetaData requestMetaData) { - return new BodyCachingServerHttpResponseDecorator(response, trafficSelector, metaDataFactory, requestMetaData); - } -} diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index f9e1e89..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.getyourguide.openapi.validation.autoconfigure.SpringWebFluxLibraryAutoConfiguration diff --git a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfigurationApplicationContextTest.java b/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfigurationApplicationContextTest.java deleted file mode 100644 index b7e641d..0000000 --- a/spring-boot-starter/spring-boot-starter-webflux-spring2.7/src/test/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebFluxLibraryAutoConfigurationApplicationContextTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.getyourguide.openapi.validation.autoconfigure; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.getyourguide.openapi.validation.filter.OpenApiValidationWebFilter; -import java.util.Optional; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; -import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.mock.web.MockServletContext; - -class SpringWebFluxLibraryAutoConfigurationApplicationContextTest { - - private ConfigurableApplicationContext context; - - @AfterEach - void tearDown() { - Optional.ofNullable(context) - .ifPresent(ConfigurableApplicationContext::close); - } - - @Test - void webApplicationWithServletContext() { - context = servletWebApplicationContext(); - - assertThat(context.getBeansOfType(OpenApiValidationWebFilter.class)).size().isEqualTo(0); - } - - @Test - void webApplicationWithReactiveContext() { - context = reactiveWebApplicationContext(); - - assertThat(context.getBeansOfType(OpenApiValidationWebFilter.class)).size().isEqualTo(1); - } - - @Test - void nonWebApplicationContextShouldHaveNoFilterBeans() { - context = nonWebApplicationContext(); - - assertThat(context.getBeansOfType(OpenApiValidationWebFilter.class)).size().isEqualTo(0); - } - - private AnnotationConfigServletWebApplicationContext servletWebApplicationContext() { - var servletContext = new AnnotationConfigServletWebApplicationContext(); - - servletContext.register(SpringWebFluxLibraryAutoConfiguration.class, LibraryAutoConfiguration.class, FallbackLibraryAutoConfiguration.class); - servletContext.setServletContext(new MockServletContext()); - servletContext.refresh(); - - return servletContext; - } - - private AnnotationConfigReactiveWebApplicationContext reactiveWebApplicationContext() { - var reactiveContext = new AnnotationConfigReactiveWebApplicationContext(); - - reactiveContext.register(SpringWebFluxLibraryAutoConfiguration.class, LibraryAutoConfiguration.class, FallbackLibraryAutoConfiguration.class); - reactiveContext.refresh(); - - return reactiveContext; - } - - private AnnotationConfigApplicationContext nonWebApplicationContext() { - var reactiveContext = new AnnotationConfigApplicationContext(); - - reactiveContext.register(SpringWebFluxLibraryAutoConfiguration.class, LibraryAutoConfiguration.class, FallbackLibraryAutoConfiguration.class); - reactiveContext.refresh(); - - return reactiveContext; - } -}