Skip to content

Commit 2cccbb4

Browse files
authored
bugfix: encoded query parameter (web) (#18)
1 parent 9f0f10e commit 2cccbb4

File tree

8 files changed

+56
-17
lines changed

8 files changed

+56
-17
lines changed

examples/example-spring-boot-starter-web/src/main/resources/application.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
openapi.validation.sample-rate=0.5
1+
openapi.validation.sample-rate=1
22
openapi.validation.specification-file-path=openapi.yaml
33
openapi.validation.excluded-paths=/_readiness,/_liveness,/_metrics
44
openapi.validation.validation-report-throttle-wait-seconds=10

examples/example-spring-boot-starter-webflux/src/main/resources/application.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
openapi.validation.sample-rate=0.5
1+
openapi.validation.sample-rate=1
22
openapi.validation.specification-file-path=openapi.yaml
33
openapi.validation.excluded-paths=/_readiness,/_liveness,/_metrics
44
openapi.validation.validation-report-throttle-wait-seconds=10

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

+12-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import com.getyourguide.openapi.validation.api.model.RequestMetaData;
1010
import com.getyourguide.openapi.validation.api.model.ResponseMetaData;
1111
import com.getyourguide.openapi.validation.api.model.ValidationResult;
12-
import com.getyourguide.openapi.validation.api.model.ValidatorConfiguration;
1312
import com.getyourguide.openapi.validation.core.validator.OpenApiInteractionValidatorWrapper;
13+
import java.net.URLDecoder;
1414
import java.nio.charset.StandardCharsets;
1515
import java.util.concurrent.RejectedExecutionException;
1616
import java.util.concurrent.ThreadPoolExecutor;
@@ -32,11 +32,10 @@ public OpenApiRequestValidator(
3232
ThreadPoolExecutor threadPoolExecutor,
3333
ValidationReportHandler validationReportHandler,
3434
MetricsReporter metricsReporter,
35-
String specificationFilePath,
36-
ValidatorConfiguration configuration
35+
OpenApiInteractionValidatorWrapper validator
3736
) {
3837
this.threadPoolExecutor = threadPoolExecutor;
39-
this.validator = new OpenApiInteractionValidatorFactory().build(specificationFilePath, configuration);
38+
this.validator = validator;
4039
this.validationReportHandler = validationReportHandler;
4140
this.metricsReporter = metricsReporter;
4241

@@ -79,14 +78,22 @@ public ValidationResult validateRequestObject(final RequestMetaData request, Str
7978
private static SimpleRequest buildSimpleRequest(RequestMetaData request, String requestBody) {
8079
var requestBuilder = new SimpleRequest.Builder(request.getMethod(), request.getUri().getPath());
8180
URLEncodedUtils.parse(request.getUri(), StandardCharsets.UTF_8)
82-
.forEach(p -> requestBuilder.withQueryParam(p.getName(), p.getValue()));
81+
.forEach(p -> requestBuilder.withQueryParam(p.getName(), nullSafeUrlDecode(p.getValue())));
8382
if (requestBody != null) {
8483
requestBuilder.withBody(requestBody);
8584
}
8685
request.getHeaders().forEach(requestBuilder::withHeader);
8786
return requestBuilder.build();
8887
}
8988

89+
private static String nullSafeUrlDecode(String value) {
90+
if (value == null) {
91+
return null;
92+
}
93+
94+
return URLDecoder.decode(value, StandardCharsets.UTF_8);
95+
}
96+
9097
public ValidationResult validateResponseObject(
9198
final RequestMetaData request,
9299
ResponseMetaData response,
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,72 @@
11
package com.getyourguide.openapi.validation.core;
22

3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.mockito.ArgumentMatchers.any;
35
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.verify;
47

8+
import com.atlassian.oai.validator.model.SimpleRequest;
59
import com.getyourguide.openapi.validation.api.metrics.MetricsReporter;
6-
import com.getyourguide.openapi.validation.api.model.ValidatorConfiguration;
10+
import com.getyourguide.openapi.validation.api.model.RequestMetaData;
11+
import com.getyourguide.openapi.validation.core.validator.OpenApiInteractionValidatorWrapper;
12+
import java.net.URI;
13+
import java.util.HashMap;
714
import java.util.concurrent.RejectedExecutionException;
815
import java.util.concurrent.ThreadPoolExecutor;
916
import org.junit.jupiter.api.BeforeEach;
1017
import org.junit.jupiter.api.Test;
18+
import org.mockito.ArgumentCaptor;
1119
import org.mockito.Mockito;
1220

1321
public class OpenApiRequestValidatorTest {
1422

1523
private ThreadPoolExecutor threadPoolExecutor;
24+
private OpenApiInteractionValidatorWrapper validator;
1625

1726
private OpenApiRequestValidator openApiRequestValidator;
1827

1928
@BeforeEach
2029
public void setup() {
2130
threadPoolExecutor = mock();
31+
validator = mock();
2232
ValidationReportHandler validationReportHandler = mock();
2333
MetricsReporter metricsReporter = mock();
2434

2535
openApiRequestValidator = new OpenApiRequestValidator(
2636
threadPoolExecutor,
2737
validationReportHandler,
2838
metricsReporter,
29-
"",
30-
new ValidatorConfiguration(null, null, null)
39+
validator
3140
);
3241
}
3342

3443
@Test
3544
public void testWhenThreadPoolExecutorRejectsExecutionThenItShouldNotThrow() {
36-
Mockito.doThrow(new RejectedExecutionException()).when(threadPoolExecutor).execute(Mockito.any());
45+
Mockito.doThrow(new RejectedExecutionException()).when(threadPoolExecutor).execute(any());
3746

3847
openApiRequestValidator.validateRequestObjectAsync(mock(), null);
3948
}
49+
50+
@Test
51+
public void testWhenEncodedQueryParamIsPassedThenValidationShouldHappenWithQueryParamDecoded() {
52+
var uri = URI.create("https://api.example.com?ids=1%2C2%2C3&text=e%3Dmc2%20%26%20more&spaces=this+is+a+sparta");
53+
var request = new RequestMetaData("GET", uri, new HashMap<>());
54+
55+
openApiRequestValidator.validateRequestObject(request, null);
56+
57+
var simpleRequestArgumentCaptor = ArgumentCaptor.forClass(SimpleRequest.class);
58+
verify(validator).validateRequest(simpleRequestArgumentCaptor.capture());
59+
verifyQueryParamValueEquals(simpleRequestArgumentCaptor, "ids", "1,2,3");
60+
verifyQueryParamValueEquals(simpleRequestArgumentCaptor, "text", "e=mc2 & more");
61+
verifyQueryParamValueEquals(simpleRequestArgumentCaptor, "spaces", "this is a sparta");
62+
}
63+
64+
private void verifyQueryParamValueEquals(
65+
ArgumentCaptor<SimpleRequest> simpleRequestArgumentCaptor,
66+
String name,
67+
String expected
68+
) {
69+
var ids = simpleRequestArgumentCaptor.getValue().getQueryParameterValues(name).iterator().next();
70+
assertEquals(expected, ids);
71+
}
4072
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.getyourguide.openapi.validation.api.model.ValidatorConfiguration;
1515
import com.getyourguide.openapi.validation.api.model.ValidatorConfigurationBuilder;
1616
import com.getyourguide.openapi.validation.core.DefaultViolationLogger;
17+
import com.getyourguide.openapi.validation.core.OpenApiInteractionValidatorFactory;
1718
import com.getyourguide.openapi.validation.core.OpenApiRequestValidator;
1819
import com.getyourguide.openapi.validation.core.ValidationReportHandler;
1920
import com.getyourguide.openapi.validation.core.throttle.RequestBasedValidationReportThrottler;
@@ -112,8 +113,8 @@ public OpenApiRequestValidator openApiRequestValidator(
112113
threadPoolExecutor,
113114
validationReportHandler,
114115
metricsReporter,
115-
properties.getSpecificationFilePath(),
116-
validatorConfiguration
116+
new OpenApiInteractionValidatorFactory()
117+
.build(properties.getSpecificationFilePath(), validatorConfiguration)
117118
);
118119
}
119120
}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.getyourguide.openapi.validation.api.model.RequestMetaData;
44
import com.getyourguide.openapi.validation.api.model.ResponseMetaData;
5-
import java.util.Map;
65
import java.util.TreeMap;
76
import javax.servlet.http.HttpServletRequest;
87
import javax.servlet.http.HttpServletResponse;
@@ -13,7 +12,7 @@ public class ServletMetaDataFactory {
1312
public static final String HEADER_CONTENT_TYPE = "Content-Type";
1413

1514
public RequestMetaData buildRequestMetaData(HttpServletRequest request) {
16-
var uri = ServletUriComponentsBuilder.fromRequest(request).build(Map.of());
15+
var uri = ServletUriComponentsBuilder.fromRequest(request).build().toUri();
1716
return new RequestMetaData(request.getMethod(), uri, getHeaders(request));
1817
}
1918

spring-boot-starter/spring-boot-starter-web/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ dependencies {
2222

2323
testImplementation 'org.springframework.boot:spring-boot-starter-test'
2424
testImplementation 'org.springframework:spring-web'
25+
testImplementation 'org.springframework:spring-webmvc'
2526
testImplementation 'org.apache.tomcat.embed:tomcat-embed-core' // For jakarta.servlet.ServletContext
2627
}

spring-boot-starter/spring-boot-starter-web/src/main/java/com/getyourguide/openapi/validation/factory/ServletMetaDataFactory.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.getyourguide.openapi.validation.api.model.ResponseMetaData;
55
import jakarta.servlet.http.HttpServletRequest;
66
import jakarta.servlet.http.HttpServletResponse;
7-
import java.util.Map;
87
import java.util.TreeMap;
98
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
109

@@ -13,7 +12,7 @@ public class ServletMetaDataFactory {
1312
public static final String HEADER_CONTENT_TYPE = "Content-Type";
1413

1514
public RequestMetaData buildRequestMetaData(HttpServletRequest request) {
16-
var uri = ServletUriComponentsBuilder.fromRequest(request).build(Map.of());
15+
var uri = ServletUriComponentsBuilder.fromRequest(request).build().toUri();
1716
return new RequestMetaData(request.getMethod(), uri, getHeaders(request));
1817
}
1918

0 commit comments

Comments
 (0)