diff --git a/vertx-web-api-contract/src/main/java/io/vertx/ext/web/api/validation/impl/BaseValidationHandler.java b/vertx-web-api-contract/src/main/java/io/vertx/ext/web/api/validation/impl/BaseValidationHandler.java index 4c14462bae..e0d26e36b2 100644 --- a/vertx-web-api-contract/src/main/java/io/vertx/ext/web/api/validation/impl/BaseValidationHandler.java +++ b/vertx-web-api-contract/src/main/java/io/vertx/ext/web/api/validation/impl/BaseValidationHandler.java @@ -101,12 +101,13 @@ public void handle(RoutingContext routingContext) { private Map validatePathParams(RoutingContext routingContext) throws ValidationException { // Validation process validate only params that are registered in the validation -> extra params are allowed Map parsedParams = new HashMap<>(); - Map pathParams = routingContext.pathParams(); + for (ParameterValidationRule rule : pathParamsRules.values()) { String name = rule.getName(); - if (pathParams.containsKey(name)) { - if (pathParams.get(name) != null || !rule.isOptional() ) { - RequestParameter parsedParam = rule.validateSingleParam(pathParams.get(name)); + if (name != null) { + String pathParam = routingContext.pathParam(name); + if (pathParam != null || !rule.isOptional() ) { + RequestParameter parsedParam = rule.validateSingleParam(pathParam); if (parsedParams.containsKey(parsedParam.getName())) parsedParam = parsedParam.merge(parsedParams.get(parsedParam.getName())); parsedParams.put(parsedParam.getName(), parsedParam); diff --git a/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java b/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java index fb68fdd173..a18c8b731c 100644 --- a/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java +++ b/vertx-web/src/main/java/io/vertx/ext/web/RoutingContext.java @@ -613,7 +613,20 @@ default LanguageHeader preferredLanguage() { } /** - * Returns a map of named parameters as defined in path declaration with their actual values + * Add a new one or replace an existing path parameter + * @throws NullPointerException when the name or value is null + */ + void addOrReplacePathParam(String name, String value); + + /** + * Remove a path parameter. + * + * @return {@code true} when removed, {@code false} otherwise + */ + boolean removePathParam(String s); + + /** + * Returns an unmodifiable map of named parameters as defined in path declaration with their actual values * * @return the map of named parameters */ diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java index 9221b2ab7c..736e9d8800 100644 --- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java +++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RouteState.java @@ -1001,8 +1001,7 @@ public int matches(RoutingContextImplBase context, String mountPoint, boolean fa } if (pattern != null) { // need to reset "rest" - context.pathParams() - .remove("*"); + context.removePathParam("*"); String path = useNormalizedPath ? context.normalizedPath() : context.request().path(); @@ -1031,8 +1030,7 @@ public int matches(RoutingContextImplBase context, String mountPoint, boolean fa if (!exactPath) { context.matchRest = m.start("rest"); // always replace - context.pathParams() - .put("*", path.substring(context.matchRest)); + context.addOrReplacePathParam("*", path.substring(context.matchRest)); } if (!isEmpty(groups)) { @@ -1163,8 +1161,7 @@ private boolean pathMatches(String mountPoint, RoutingContext ctx) { if (exactPath) { // exact path has no "rest" - ctx.pathParams() - .remove("*"); + ctx.removePathParam("*"); return pathMatchesExact(thePath, requestPath, pathEndsWithSlash); } else { @@ -1185,8 +1182,7 @@ private boolean pathMatches(String mountPoint, RoutingContext ctx) { // because the mount path ended with a wildcard we are relaxed in the check if (thePath.regionMatches(0, requestPath, 0, pathLen - 1)) { // handle the "rest" as path param *, always known to be empty - ctx.pathParams() - .put("*", "/"); + ctx.addOrReplacePathParam("*", "/"); return true; } } @@ -1194,8 +1190,8 @@ private boolean pathMatches(String mountPoint, RoutingContext ctx) { if (requestPath.startsWith(thePath)) { // handle the "rest" as path param * - ctx.pathParams() - .put("*", URIDecoder.decodeURIComponent(requestPath.substring(thePath.length()), false)); + ctx.addOrReplacePathParam("*", + URIDecoder.decodeURIComponent(requestPath.substring(thePath.length()), false)); return true; } return false; @@ -1268,7 +1264,7 @@ private void addPathParam(RoutingContext context, String name, String value) { if (!request.params().contains(name)) { request.params().add(name, decodedValue); } - context.pathParams().put(name, decodedValue); + context.addOrReplacePathParam(name, decodedValue); } boolean hasNextContextHandler(RoutingContextImplBase context) { diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java index b598376fdb..a88caaa08a 100644 --- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java +++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextDecorator.java @@ -254,6 +254,16 @@ public void reroute(HttpMethod method, String path) { decoratedContext.reroute(method, path); } + @Override + public void addOrReplacePathParam(String name, String value) { + decoratedContext.addOrReplacePathParam(name, value); + } + + @Override + public boolean removePathParam(String s) { + return decoratedContext.removePathParam(s); + } + @Override public Map pathParams() { return decoratedContext.pathParams(); diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java index adf82bc078..2cc4e7238d 100644 --- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java +++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextImpl.java @@ -36,6 +36,7 @@ import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import static io.vertx.ext.web.handler.impl.SessionHandlerImpl.SESSION_USER_HOLDER_KEY; @@ -439,14 +440,41 @@ public void reroute(HttpMethod method, String path) { restart(); } + @Override + public void addOrReplacePathParam(final String name, final String value) { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(value, "value"); + getOrCreatePathParams().put(name, value); + } + @Override public Map pathParams() { - return getPathParams(); + if (pathParams == null || pathParams.isEmpty()) { + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(pathParams); + } + + @Override + public boolean removePathParam(String s) { + if (s == null) { + return false; + } + if (pathParams != null) { + return pathParams.remove(s) != null; + } + return false; } @Override public @Nullable String pathParam(String name) { - return getPathParams().get(name); + if (name == null) { + return null; + } + if (pathParams != null) { + return pathParams.get(name); + } + return null; } @Override @@ -490,9 +518,10 @@ private MultiMap getQueryParams(Charset charset) { return queryParams; } - private Map getPathParams() { + private Map getOrCreatePathParams() { if (pathParams == null) { - pathParams = new HashMap<>(); + // let's start small + pathParams = new HashMap<>(1); } return pathParams; } diff --git a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java index f6c7bed7d1..256857e3be 100644 --- a/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java +++ b/vertx-web/src/main/java/io/vertx/ext/web/impl/RoutingContextWrapper.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; /** * @author Tim Fox @@ -308,6 +309,16 @@ public void reroute(HttpMethod method, String path) { inner.reroute(method, path); } + @Override + public void addOrReplacePathParam(final String name, final String value) { + inner.addOrReplacePathParam(name, value); + } + + @Override + public boolean removePathParam(final String s) { + return inner.removePathParam(s); + } + @Override public Map pathParams() { return inner.pathParams(); diff --git a/vertx-web/src/test/java/io/vertx/ext/web/RouterTest.java b/vertx-web/src/test/java/io/vertx/ext/web/RouterTest.java index 9310f91695..ae982dd734 100644 --- a/vertx-web/src/test/java/io/vertx/ext/web/RouterTest.java +++ b/vertx-web/src/test/java/io/vertx/ext/web/RouterTest.java @@ -863,8 +863,7 @@ public void testPercentEncoding() throws Exception { @Test public void testPathParamsAreFulfilled() throws Exception { router.route("/blah/:abc/quux/:def/eep/:ghi").handler(rc -> { - Map params = rc.pathParams(); - rc.response().setStatusMessage(params.get("abc") + params.get("def") + params.get("ghi")).end(); + rc.response().setStatusMessage(rc.pathParam("abc") + rc.pathParam("def") + rc.pathParam("ghi")).end(); }); testPattern("/blah/tim/quux/julien/eep/nick", "timjuliennick"); } @@ -877,11 +876,10 @@ public void testPathParamsDoesNotOverrideQueryParam() throws Exception { final String queryParamValue2 = "queryParamValue2"; final String sep = ","; router.route("/blah/:" + paramName + "/test").handler(rc -> { - Map params = rc.pathParams(); MultiMap queryParams = rc.request().params(); List values = queryParams.getAll(paramName); String qValue = values.stream().collect(Collectors.joining(sep)); - rc.response().setStatusMessage(params.get(paramName) + "|" + qValue).end(); + rc.response().setStatusMessage(rc.pathParam(paramName) + "|" + qValue).end(); }); testRequest(HttpMethod.GET, "/blah/" + pathParamValue + "/test?" + paramName + "=" + queryParamValue1 + "&" + paramName + "=" + queryParamValue2,