Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,102 +1,57 @@
/*
Original Copyright Headers
This file has been modified, but copied from
https://github.com/RestExpress/RestExpress/blob/master/core/src/main/java/org/restexpress/url/UrlPattern.java
This file include a substantially simplified version of
https://github.com/wilkincheung/URI-Template-Pattern-Matcher/blob/master/src/main/java/com/prodigi/service/UriTemplateValidator.java
*/
/**
* Copyright 2010, Strategic Gains, Inc.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.quarkiverse.openapi.generator.providers;
The MIT License (MIT)
Copyright (c) 2015 Wilkin Cheung

import java.util.regex.Pattern;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

/**
* PathPatternMatcher leverages Regex Pattern to represent a parameterized URL. Parameters within the URL are
* denoted by curly braces '{}' with the parameter name contained within (e.g. '{userid}').
* <p>
* <p/>
* Parameter names must be formed of word characters (e.g. A-Z, a-z, 0-9, '_').
* <p/>
* An optional format parameter following a dot ('.') may be added to the end. While it could be named any valid parameter name,
* RestExpress offers special handling (e.g. within the Request, etc.) if it's named 'format'.
* <p/>
* Note that the format specifier allows only word characters and percent-encoded characters.
* <p>
* <p/>
* URL Pattern examples:
* <ul>
* <li>/api/search.{format}</li>
* <li>/api/search/users/{userid}.{format}</li>
* <li>/api/{version}/search/users/{userid}</li>
* </ul>
* <p>
* RestExpress parses URI paths which is described in the URI Generic Syntax IETF RFC 3986 specification,
* section 3.3 (http://tools.ietf.org/html/rfc3986#section-3.3). RestExpress parses paths into segments
* separated by slashes ("/"), the segments of which are composed of unreserved, percent encoded,
* sub-delimiters, colon (":") or ampersand ("@"), each of which are defined below (from the spec):
* <p/>
* pct-encoded = "%" HEXDIG HEXDIG
* <p/>
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"<br/>
* reserved = gen-delims / sub-delims<br/>
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"</br>
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" *
* <p/>
* In other words, RestExpress accepts path segments containing: [A-Z] [a-z] [0-9] % - . _ ~ ! $ & ' ( ) * + , ; = : @
* <p/>
* RestExpress also accepts square brackets ('[' and ']'), but this is deprecated and not recommended.
*
* @author toddf
* @see <a href="Uniform Resource Identifier (URI): Generic Syntax">http://www.ietf.org/rfc/rfc3986.txt</a>
* @since Apr 28, 2010
*/
public class UrlPatternMatcher {
// Finds parameters in the URL pattern string.
private static final String URL_PARAM_REGEX = "\\{(\\S*?)\\}";
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

// Replaces parameter names in the URL pattern string to match parameters in URLs.
private static final String URL_PARAM_MATCH_REGEX = "\\([%\\\\w-.\\\\~!\\$&'\\\\(\\\\)\\\\*\\\\+,;=:\\\\[\\\\]@]+?\\)";
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package io.quarkiverse.openapi.generator.providers;

// Finds the 'format' portion of the URL pattern string.
private static final String URL_FORMAT_REGEX = "(?:\\.\\{format\\})$";
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// Replaces the format parameter name in the URL pattern string to match the format specifier in URLs. Appended to the end of the regex string
// when a URL pattern contains a format parameter.
private static final String URL_FORMAT_MATCH_REGEX = "(?:\\\\.\\([\\\\w%]+?\\))?";
public class UrlPatternMatcher {

// Finds the query string portion within a URL. Appended to the end of the built-up regex string.
// For each pattern {keyName} replaces it with (.*)
private static final Pattern LEVEL_ONE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
// Replaces each {keyName} with (.*)
private static final String REPLACES_WITH = "(.*)";
private static final String URL_QUERY_STRING_REGEX = "(?:\\?.*?)?$";

/**
* The URL pattern describing the URL layout and any parameters.
*/
private final String urlPattern;

/**
* A compiled regex created from the urlPattern, above.
*/
private Pattern compiledUrl;
private final Pattern pattern;

/**
* @param pattern
*/
public UrlPatternMatcher(String pattern) {
this.urlPattern = pattern;
String parsedPattern = this.urlPattern.replaceFirst(URL_FORMAT_REGEX, URL_FORMAT_MATCH_REGEX);
parsedPattern = parsedPattern.replaceAll(URL_PARAM_REGEX, URL_PARAM_MATCH_REGEX);
this.compiledUrl = Pattern.compile(parsedPattern + URL_QUERY_STRING_REGEX);
public UrlPatternMatcher(String uriTemplate) {
StringBuilder patternBuilder = new StringBuilder();
Matcher m = LEVEL_ONE_PATTERN.matcher(uriTemplate);
int end = 0;
while (m.find()) {
// In each loop, find next pattern in URI that is "{keyName}"
// If found,append the substring to patternBuilder.
patternBuilder.append(Pattern.quote(uriTemplate.substring(end, m.start()))).append(REPLACES_WITH);
end = m.end();
}
patternBuilder.append(Pattern.quote(uriTemplate.substring(end, uriTemplate.length())));
this.pattern = Pattern.compile(patternBuilder + URL_QUERY_STRING_REGEX);
}

/**
Expand All @@ -107,6 +62,10 @@ public UrlPatternMatcher(String pattern) {
* @return true if the given URL matches the underlying pattern. Otherwise false.
*/
public boolean matches(String url) {
return compiledUrl.matcher(url).matches();
return pattern.matcher(url).matches();
}

public String toString() {
return pattern.toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ void verifyPathsMatch(final String pathPattern, final String requestPath) {
Assertions.assertTrue(pattern.matches(requestPath));
}

@ParameterizedTest
@MethodSource("providePathsThatNotMatch")
void verifyPathsNotMatch(final String pathPattern, final String requestPath) {
UrlPatternMatcher pattern = new UrlPatternMatcher(pathPattern);
Assertions.assertFalse(pattern.matches(requestPath));
}

private static Stream<Arguments> providePathsThatMatch() {
return Stream.of(
Arguments.of("/pets/{id}", "/pets/1"),
Expand All @@ -32,7 +39,16 @@ private static Stream<Arguments> providePathsThatMatch() {
Arguments.of("/{id}/{foo}/{id2}", "/1/2/3?q=1&q2=2"),
Arguments.of("/{id}/{foo}/{id2}", "/1/2/3"),
Arguments.of("/v2/pets/{id}", "/v2/pets/1"),
Arguments.of("/pets/{pet-id}/types/{type-id}", "/pets/1/types/2"));
Arguments.of("/pets/{pet-id}/types/{type-id}", "/pets/1/types/2"),
Arguments.of("/repos/{ref}", "/repos/prefixed/cool.sw"),
Arguments.of("/repos/{ref}/pepe", "/repos/prefixed/cool.sw/pepe"),
Arguments.of("pepe/pepa/pepu", "pepe/pepa/pepu"));
}

private static Stream<Arguments> providePathsThatNotMatch() {
return Stream.of(
Arguments.of("/pets/{id}", "/pes/1"),
Arguments.of("/{id}/pepe", "/1/2/pep"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case it doesn't match because "pep" vs "pepe", but however
Arguments.of("/{id}/pepe", "/1/2/pepe"));
matches, is it correct? The placeholder should only contain one id, not two, wdyt?

}

}