diff --git a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/model/ApiPathImpl.java b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/model/ApiPathImpl.java index 372e589d..1acd36f4 100644 --- a/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/model/ApiPathImpl.java +++ b/openapi-request-validator-core/src/main/java/com/atlassian/oai/validator/model/ApiPathImpl.java @@ -33,6 +33,9 @@ public class ApiPathImpl extends NormalisedPathImpl implements ApiPath { private final boolean strictPathMatching; + /** Compiled once per part - the path template is fixed for the lifetime of this instance. */ + private final Pattern[] partPatterns; + public ApiPathImpl(@Nonnull final String path, @Nullable final String apiPrefix) { this(path, apiPrefix, false); } @@ -45,6 +48,10 @@ public ApiPathImpl(@Nonnull final String path, final boolean strictPathMatching) { super(path, apiPrefix); this.strictPathMatching = strictPathMatching; + this.partPatterns = new Pattern[numberOfParts()]; + for (int i = 0; i < partPatterns.length; i++) { + partPatterns[i] = compile(quote(part(i)).replaceAll(PARAM_REGEX, "\\\\E(.*?)\\\\Q"), CASE_INSENSITIVE); + } } @Override @@ -66,9 +73,7 @@ public boolean matches(final NormalisedPath requestPath) { @Override public boolean partMatches(final int index, @Nonnull final String requestPathPart) { requireNonNull(requestPathPart, "A request path part is required"); - final String template = part(index); - final Pattern templatePattern = compile(quote(template).replaceAll(PARAM_REGEX, "\\\\E(.*?)\\\\Q"), CASE_INSENSITIVE); - return templatePattern.matcher(requestPathPart).matches(); + return partPatterns[index].matcher(requestPathPart).matches(); } @Override diff --git a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/model/ApiPathImplTest.java b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/model/ApiPathImplTest.java index fc968557..705dfa8f 100644 --- a/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/model/ApiPathImplTest.java +++ b/openapi-request-validator-core/src/test/java/com/atlassian/oai/validator/model/ApiPathImplTest.java @@ -10,6 +10,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; public class ApiPathImplTest { @@ -212,6 +213,44 @@ public void matches_doesNotMatches_whenBadParamMatch() { assertThat(matches("/p1/p2/{param1}-{param2}", "/p1/p2/floop"), is(false)); } + @Test + public void partMatches_matchesLiterally_whenPartContainsRegexMetacharacters() { + assertThat(partMatches("a.b", "a.b"), is(true)); + assertThat(partMatches("a.b", "axb"), is(false)); + assertThat(partMatches("a+b", "a+b"), is(true)); + assertThat(partMatches("a+b", "aab"), is(false)); + } + + @Test + public void partMatches_throws_whenIndexOutOfRange() { + final ApiPathImpl classUnderTest = new ApiPathImpl("/{id}", null); + + assertThrows(IndexOutOfBoundsException.class, () -> classUnderTest.partMatches(1, "foop")); + } + + @Test + public void partMatches_isRepeatable_onTheSameInstance() { + final ApiPathImpl classUnderTest = new ApiPathImpl("/{id}/literal/a.b/a+b", null); + + assertThat(classUnderTest.partMatches(0, "foop"), is(true)); + assertThat(classUnderTest.partMatches(1, "literal"), is(true)); + assertThat(classUnderTest.partMatches(1, "LITERAL"), is(true)); + assertThat(classUnderTest.partMatches(1, "blarp"), is(false)); + assertThat(classUnderTest.partMatches(2, "a.b"), is(true)); + assertThat(classUnderTest.partMatches(2, "axb"), is(false)); + assertThat(classUnderTest.partMatches(3, "a+b"), is(true)); + assertThat(classUnderTest.partMatches(3, "aab"), is(false)); + } + + @Test + public void matches_isRepeatable_onTheSameInstance() { + final ApiPathImpl classUnderTest = new ApiPathImpl("/p1/{param1}/p3", null); + + assertThat(classUnderTest.matches(new NormalisedPathImpl("/p1/foop/p3", null)), is(true)); + assertThat(classUnderTest.matches(new NormalisedPathImpl("/p1/foop/P3", null)), is(true)); + assertThat(classUnderTest.matches(new NormalisedPathImpl("/p1/foop/blarp", null)), is(false)); + } + private static void testParamValueExtraction(final String expression, final String path, final String... expected) { assertThat(new ApiPathImpl(expression, null).paramValues(0, path).values(), containsInAnyOrder(stream(expected).map(e -> is(ofNullable(e))).collect(toList())));