Skip to content

Commit d70f4b9

Browse files
authored
Support fail on request/response violation (#3)
1 parent 08afebc commit d70f4b9

File tree

25 files changed

+1153
-51
lines changed

25 files changed

+1153
-51
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ openapi.validation.sample-rate=1.0
8383

8484
# Custom location of specification file within resources or filesystem.
8585
openapi.validation.specification-file-path=/tmp/openapi-spec/openapi.json
86+
# If it is within src/main/resources/folder/my-spec.json use
87+
openapi.validation.specification-file-path=folder/my-spec.json
8688

8789
# Comma separated list of paths to be excluded from validation. Default is no excluded paths
8890
openapi.validation.excluded-paths=/_readiness,/_liveness,/_metrics
@@ -98,6 +100,10 @@ openapi.validation.validation-report-metric-name=openapi.violation
98100
# Add additional tags to be logged with metrics. They should be in the format {KEY}={VALUE},{KEY}={VALUE}
99101
# Default is no additional tags.
100102
openapi.validation.validation-report-metric-additional-tags=service=example,team=chk
103+
104+
# Fail requests on request/response violations. Defaults to false.
105+
openapi.validation.should-fail-on-request-violation=true
106+
openapi.validation.should-fail-on-response-violation=true
101107
```
102108

103109
### DataDog metrics

openapi-validation-api/src/main/java/com/getyourguide/openapi/validation/api/log/LoggerExtension.java

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.util.Map;
55
import lombok.NonNull;
66

7-
// TODO CHK-8357 can we get rid of this one?
87
public interface LoggerExtension {
98
Closeable addToLoggingContext(@NonNull Map<String, String> newTags);
109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.getyourguide.openapi.validation.api.model;
2+
3+
public enum ValidationResult { VALID, INVALID, NOT_APPLICABLE }

openapi-validation-api/src/main/java/com/getyourguide/openapi/validation/api/selector/DefaultTrafficSelector.java

+24
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,24 @@ public class DefaultTrafficSelector implements TrafficSelector {
1111

1212
private final double sampleRate;
1313
private final Set<String> excludedPaths;
14+
private final Boolean shouldFailOnRequestViolation;
15+
private final Boolean shouldFailOnResponseViolation;
1416

1517
public DefaultTrafficSelector(Double sampleRate, Set<String> excludedPaths) {
18+
this(sampleRate, excludedPaths, null, null);
19+
}
20+
21+
public DefaultTrafficSelector(
22+
Double sampleRate,
23+
Set<String> excludedPaths,
24+
Boolean shouldFailOnRequestViolation,
25+
Boolean shouldFailOnResponseViolation
26+
) {
1627
this.sampleRate = sampleRate != null ? sampleRate : SAMPLE_RATE_DEFAULT;
1728
this.excludedPaths = excludedPaths != null ? excludedPaths : Set.of();
29+
this.shouldFailOnRequestViolation = shouldFailOnRequestViolation != null ? shouldFailOnRequestViolation : false;
30+
this.shouldFailOnResponseViolation =
31+
shouldFailOnResponseViolation != null ? shouldFailOnResponseViolation : false;
1832
}
1933

2034
@Override
@@ -40,6 +54,16 @@ public boolean canResponseBeValidated(RequestMetaData request, ResponseMetaData
4054
&& isContentTypeSupported(response.getContentType());
4155
}
4256

57+
@Override
58+
public boolean shouldFailOnRequestViolation(RequestMetaData request) {
59+
return shouldFailOnRequestViolation;
60+
}
61+
62+
@Override
63+
public boolean shouldFailOnResponseViolation(RequestMetaData request) {
64+
return shouldFailOnResponseViolation;
65+
}
66+
4367
private boolean isExcludedRequest(RequestMetaData request) {
4468
return excludedPaths.contains(request.getUri().getPath());
4569
}

openapi-validation-api/src/main/java/com/getyourguide/openapi/validation/api/selector/TrafficSelector.java

+8
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ default boolean canRequestBeValidated(RequestMetaData request) {
1313
default boolean canResponseBeValidated(RequestMetaData request, ResponseMetaData response) {
1414
return true;
1515
}
16+
17+
default boolean shouldFailOnRequestViolation(RequestMetaData request) {
18+
return false;
19+
}
20+
21+
default boolean shouldFailOnResponseViolation(RequestMetaData request) {
22+
return false;
23+
}
1624
}

openapi-validation-core/src/main/java/com/getyourguide/openapi/validation/core/OpenApiRequestValidator.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import com.atlassian.oai.validator.model.Request;
44
import com.atlassian.oai.validator.model.SimpleRequest;
55
import com.atlassian.oai.validator.model.SimpleResponse;
6+
import com.atlassian.oai.validator.report.ValidationReport;
67
import com.getyourguide.openapi.validation.api.model.Direction;
78
import com.getyourguide.openapi.validation.api.model.RequestMetaData;
89
import com.getyourguide.openapi.validation.api.model.ResponseMetaData;
10+
import com.getyourguide.openapi.validation.api.model.ValidationResult;
911
import com.getyourguide.openapi.validation.api.model.ValidatorConfiguration;
1012
import com.getyourguide.openapi.validation.core.validator.OpenApiInteractionValidatorWrapper;
1113
import java.nio.charset.StandardCharsets;
@@ -39,13 +41,15 @@ public void validateResponseObjectAsync(final RequestMetaData request, ResponseM
3941
threadPool.execute(() -> validateResponseObject(request, response, responseBody));
4042
}
4143

42-
private void validateRequestObject(final RequestMetaData request, String requestBody) {
44+
public ValidationResult validateRequestObject(final RequestMetaData request, String requestBody) {
4345
try {
4446
var simpleRequest = buildSimpleRequest(request, requestBody);
4547
var result = validator.validateRequest(simpleRequest);
4648
validationReportHandler.handleValidationReport(request, Direction.REQUEST, requestBody, result);
49+
return buildValidationResult(result);
4750
} catch (Exception e) {
4851
log.error("Could not validate request", e);
52+
return ValidationResult.NOT_APPLICABLE;
4953
}
5054
}
5155

@@ -60,7 +64,11 @@ private static SimpleRequest buildSimpleRequest(RequestMetaData request, String
6064
return requestBuilder.build();
6165
}
6266

63-
private void validateResponseObject(final RequestMetaData request, ResponseMetaData response, final String responseBody) {
67+
public ValidationResult validateResponseObject(
68+
final RequestMetaData request,
69+
ResponseMetaData response,
70+
final String responseBody
71+
) {
6472
try {
6573
var responseBuilder = new SimpleResponse.Builder(response.getStatusCode());
6674
response.getHeaders().forEach(responseBuilder::withHeader);
@@ -75,8 +83,22 @@ private void validateResponseObject(final RequestMetaData request, ResponseMetaD
7583
responseBuilder.build()
7684
);
7785
validationReportHandler.handleValidationReport(request, Direction.RESPONSE, responseBody, result);
86+
return buildValidationResult(result);
7887
} catch (Exception e) {
7988
log.error("Could not validate response", e);
89+
return ValidationResult.NOT_APPLICABLE;
8090
}
8191
}
92+
93+
private ValidationResult buildValidationResult(ValidationReport validationReport) {
94+
if (validationReport == null) {
95+
return ValidationResult.NOT_APPLICABLE;
96+
}
97+
98+
if (validationReport.getMessages().isEmpty()) {
99+
return ValidationResult.VALID;
100+
}
101+
102+
return ValidationResult.INVALID;
103+
}
82104
}

spring-boot-starter/spring-boot-starter-core/src/main/java/com/getyourguide/openapi/validation/OpenApiValidationApplicationProperties.java

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public class OpenApiValidationApplicationProperties {
2727
private String validationReportMetricName;
2828
private String validationReportMetricAdditionalTags;
2929
private String excludedPaths;
30+
private Boolean shouldFailOnRequestViolation;
31+
private Boolean shouldFailOnResponseViolation;
3032

3133
public List<MetricTag> getValidationReportMetricAdditionalTags() {
3234
if (validationReportMetricAdditionalTags == null) {

spring-boot-starter/spring-boot-starter-core/src/main/java/com/getyourguide/openapi/validation/autoconfigure/FallbackLibraryAutoConfiguration.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ public class FallbackLibraryAutoConfiguration {
2121
@Bean
2222
@ConditionalOnMissingBean
2323
public TrafficSelector defaultTrafficSelector() {
24-
return new DefaultTrafficSelector(properties.getSampleRate(), properties.getExcludedPathsAsSet());
24+
return new DefaultTrafficSelector(
25+
properties.getSampleRate(),
26+
properties.getExcludedPathsAsSet(),
27+
properties.getShouldFailOnRequestViolation(),
28+
properties.getShouldFailOnResponseViolation()
29+
);
2530
}
2631

2732
}

spring-boot-starter/spring-boot-starter-core/src/main/resources/META-INF/spring-configuration-metadata.json

+10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
"name": "openapi.validation.validation-report-metric-additional-tags",
3030
"type": "java.lang.String",
3131
"description": "Additional tags to be logged with metrics. They should be in the format {KEY}={VALUE},{KEY}={VALUE}. Default is no additional tags."
32+
},
33+
{
34+
"name": "openapi.validation.should-fail-on-request-violation",
35+
"type": "java.lang.Boolean",
36+
"description": "If set to true the request will fail in case a request violation occurs. Defaults to false."
37+
},
38+
{
39+
"name": "openapi.validation.should-fail-on-response-violation",
40+
"type": "java.lang.Boolean",
41+
"description": "If set to true the request will fail in case a response violation occurs. Defaults to false."
3242
}
3343
]
3444
}

spring-boot-starter/spring-boot-starter-core/src/test/java/com/getyourguide/openapi/validation/OpenApiValidationApplicationPropertiesTest.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.getyourguide.openapi.validation;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
46

57
import com.getyourguide.openapi.validation.api.metrics.MetricTag;
68
import java.util.List;
@@ -24,7 +26,9 @@ void getters() {
2426
VALIDATION_REPORT_THROTTLE_WAIT_SECONDS,
2527
VALIDATION_REPORT_METRIC_NAME,
2628
VALIDATION_REPORT_METRIC_ADDITONAL_TAGS_STRING,
27-
EXCLUDED_PATHS
29+
EXCLUDED_PATHS,
30+
true,
31+
false
2832
);
2933

3034
assertEquals(SAMPLE_RATE, loggingConfiguration.getSampleRate());
@@ -37,5 +41,7 @@ void getters() {
3741
);
3842
assertEquals(EXCLUDED_PATHS, loggingConfiguration.getExcludedPaths());
3943
assertEquals(Set.of("/_readiness","/_liveness","/_metrics"), loggingConfiguration.getExcludedPathsAsSet());
44+
assertTrue(loggingConfiguration.getShouldFailOnRequestViolation());
45+
assertFalse(loggingConfiguration.getShouldFailOnResponseViolation());
4046
}
4147
}

spring-boot-starter/spring-boot-starter-web-spring2.7/src/main/java/com/getyourguide/openapi/validation/autoconfigure/SpringWebLibraryAutoConfiguration.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import com.getyourguide.openapi.validation.api.selector.TrafficSelector;
66
import com.getyourguide.openapi.validation.core.OpenApiRequestValidator;
7+
import com.getyourguide.openapi.validation.factory.ContentCachingWrapperFactory;
78
import com.getyourguide.openapi.validation.factory.ServletMetaDataFactory;
89
import com.getyourguide.openapi.validation.filter.OpenApiValidationHttpFilter;
910
import lombok.AllArgsConstructor;
@@ -21,13 +22,20 @@ public ServletMetaDataFactory servletMetaDataFactory() {
2122
return new ServletMetaDataFactory();
2223
}
2324

25+
@Bean
26+
@ConditionalOnWebApplication(type = Type.SERVLET)
27+
public ContentCachingWrapperFactory contentCachingWrapperFactory() {
28+
return new ContentCachingWrapperFactory();
29+
}
30+
2431
@Bean
2532
@ConditionalOnWebApplication(type = Type.SERVLET)
2633
public OpenApiValidationHttpFilter openApiValidationHttpFilter(
2734
OpenApiRequestValidator validator,
2835
TrafficSelector trafficSelector,
29-
ServletMetaDataFactory metaDataFactory
36+
ServletMetaDataFactory metaDataFactory,
37+
ContentCachingWrapperFactory contentCachingWrapperFactory
3038
) {
31-
return new OpenApiValidationHttpFilter(validator, trafficSelector, metaDataFactory);
39+
return new OpenApiValidationHttpFilter(validator, trafficSelector, metaDataFactory, contentCachingWrapperFactory);
3240
}
3341
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.getyourguide.openapi.validation.factory;
2+
3+
import javax.servlet.http.HttpServletRequest;
4+
import javax.servlet.http.HttpServletResponse;
5+
import org.springframework.web.util.ContentCachingRequestWrapper;
6+
import org.springframework.web.util.ContentCachingResponseWrapper;
7+
8+
public class ContentCachingWrapperFactory {
9+
public ContentCachingRequestWrapper buildContentCachingRequestWrapper(HttpServletRequest request) {
10+
return new ContentCachingRequestWrapper(request);
11+
}
12+
13+
public ContentCachingResponseWrapper buildContentCachingResponseWrapper(HttpServletResponse response) {
14+
return new ContentCachingResponseWrapper(response);
15+
}
16+
}

0 commit comments

Comments
 (0)