Skip to content

Commit

Permalink
refactor!: Add support for parsing query string in QueryParameters (#…
Browse files Browse the repository at this point in the history
…10521)

A minor API behavior change: `Location:getQueryParameters` is unified with how `HttpServletRequest` works:
For a query string `?foo&bar` a list containing `""`  is returned for both `foo` and `bar` whereas the existing implementation returned empty lists.

This is a prerequisite for #10431
  • Loading branch information
Artur- committed Apr 6, 2021
1 parent bb50d0e commit a868f2f
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 52 deletions.
42 changes: 1 addition & 41 deletions flow-server/src/main/java/com/vaadin/flow/router/Location.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

Expand All @@ -40,7 +39,6 @@
public class Location implements Serializable {
private static final String PATH_SEPARATOR = "/";
private static final String QUERY_SEPARATOR = "?";
private static final String PARAMETERS_SEPARATOR = "&";

private final List<String> segments;
private final QueryParameters queryParameters;
Expand Down Expand Up @@ -228,45 +226,7 @@ private static QueryParameters parseParams(String path) {
query = path.substring(beginIndex + 1);
}

Map<String, List<String>> parsedParams = Arrays
.stream(query.split(PARAMETERS_SEPARATOR))
.map(Location::makeQueryParamList)
.collect(Collectors.toMap(list -> list.get(0),
Location::getParameterValues, Location::mergeLists));
return new QueryParameters(parsedParams);
}

private static List<String> makeQueryParamList(String paramAndValue) {
int index = paramAndValue.indexOf('=');
if (index == -1) {
return Collections.singletonList(paramAndValue);
}
String param = paramAndValue.substring(0, index);
String value = paramAndValue.substring(index + 1);
return Arrays.asList(param, value);
}

private static List<String> getParameterValues(List<String> paramAndValue) {
if (paramAndValue.size() == 1) {
return Collections.emptyList();
} else {
return Collections.singletonList(paramAndValue.get(1));
}
}

private static List<String> mergeLists(List<String> list1,
List<String> list2) {
List<String> result = new ArrayList<>(list1);
if (result.isEmpty()) {
result.add(null);
}
if (list2.isEmpty()) {
result.add(null);
} else {
result.addAll(list2);
}

return result;
return QueryParameters.fromString(query);
}

private static List<String> parsePath(String path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package com.vaadin.flow.router;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -97,6 +99,70 @@ private static Map<String, List<String>> toFullParameters(
entry -> Collections.singletonList(entry.getValue())));
}

/**
* Creates parameters from a query string.
* <p>
* Note that no length checking is done for the string. It is the
* responsibility of the caller (or the server) to limit the length of the
* query string.
*
* @param queryString
* the query string
* @return query parameters information
*/
public static QueryParameters fromString(String queryString) {
return new QueryParameters(parseQueryString(queryString));
}

private static Map<String, List<String>> parseQueryString(String query) {
Map<String, List<String>> parsedParams = Arrays
.stream(query.split(PARAMETERS_SEPARATOR))
.map(QueryParameters::makeQueryParamList)
.collect(Collectors.toMap(list -> list.get(0),
QueryParameters::getParameterValues,
QueryParameters::mergeLists));
return parsedParams;
}

private static List<String> makeQueryParamList(String paramAndValue) {
int index = paramAndValue.indexOf('=');
if (index == -1) {
return Collections.singletonList(paramAndValue);
}
String param = paramAndValue.substring(0, index);
String value = paramAndValue.substring(index + 1);
try {
value = URLDecoder.decode(value, "utf-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
"Unable to decode parameter value: " + value, e);
}
return Arrays.asList(param, value);
}

private static List<String> getParameterValues(List<String> paramAndValue) {
if (paramAndValue.size() == 1) {
return Collections.singletonList("");
} else {
return Collections.singletonList(paramAndValue.get(1));
}
}

private static List<String> mergeLists(List<String> list1,
List<String> list2) {
List<String> result = new ArrayList<>(list1);
if (result.isEmpty()) {
result.add(null);
}
if (list2.isEmpty()) {
result.add(null);
} else {
result.addAll(list2);
}

return result;
}

/**
* Returns query parameters information with support for multiple values
* corresponding to single parameter name.
Expand Down Expand Up @@ -125,11 +191,12 @@ public String getQueryString() {

private Stream<String> getParameterAndValues(
Entry<String, List<String>> entry) {
if (entry.getValue().isEmpty()) {
List<String> params = entry.getValue();
if (params.size() == 1 && "".equals(params.get(0))) {
return Stream.of(entry.getKey());
}
String param = entry.getKey();
return entry.getValue().stream().map(value -> value == null ? param
return params.stream().map(value -> "".equals(value) ? param
: param + PARAMETER_VALUES_SEPARATOR + value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ public void parseLocationWithQueryString_noValue() {
Location location = new Location("path?query");

assertEquals("path", location.getPath());
assertEquals(Collections.singletonMap("query", Collections.emptyList()),
assertEquals(
Collections.singletonMap("query",
Collections.singletonList("")),
location.getQueryParameters().getParameters());
assertEquals("path?query", location.getPathWithQueryParameters());
}
Expand All @@ -84,7 +86,7 @@ public void parseLocationWithQueryString_emptyValue() {
Collections.singletonMap("query",
Collections.singletonList("")),
location.getQueryParameters().getParameters());
assertEquals("path?query=", location.getPathWithQueryParameters());
assertEquals("path?query", location.getPathWithQueryParameters());
}

@Test
Expand Down Expand Up @@ -224,7 +226,7 @@ public void locationWithParamWithAndWithoutValue() {
public void locationWithParamAndEmptyValue() {
Location location = new Location("foo?param=&param=bar");

Assert.assertEquals("param=&param=bar",
Assert.assertEquals("param&param=bar",
location.getQueryParameters().getQueryString());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
import org.junit.Assert;
import org.junit.Test;

import com.vaadin.flow.router.QueryParameters;

public class QueryParametersTest {

private String simpleInputQueryString = "one=1&two=2&three=3&four&five=4%2F5%266%2B7";

private String complexInputQueryString = "one=1&one=11&two=2&two=22&three=3&four&five=4%2F5%266%2B7";

private Map<String, String> getSimpleInputParameters() {
Map<String, String> inputParameters = new HashMap<>();
inputParameters.put("one", "1");
Expand Down Expand Up @@ -93,6 +95,20 @@ public void simpleParameters() {
assertEquals(expectedFullParams, simpleParams.getParameters());
}

@Test
public void simpleParametersFromQueryString() {
QueryParameters simpleParams = QueryParameters
.fromString(simpleInputQueryString);

Map<String, List<String>> expectedFullParams = new HashMap<>();
expectedFullParams.put("one", Collections.singletonList("1"));
expectedFullParams.put("two", Collections.singletonList("2"));
expectedFullParams.put("three", Collections.singletonList("3"));
expectedFullParams.put("four", Collections.singletonList(""));
expectedFullParams.put("five", Collections.singletonList("4/5&6+7"));
assertEquals(expectedFullParams, simpleParams.getParameters());
}

@Test
public void simpleParametersToQueryString() {
QueryParameters simpleParams = QueryParameters
Expand Down Expand Up @@ -131,6 +147,20 @@ public void complexParameters() {
assertEquals(expectedFullParams, fullParams.getParameters());
}

@Test
public void complexParametersFromQueryString() {
QueryParameters fullParams = QueryParameters
.fromString(complexInputQueryString);

Map<String, List<String>> expectedFullParams = new HashMap<>();
expectedFullParams.put("one", Arrays.asList("1", "11"));
expectedFullParams.put("two", Arrays.asList("2", "22"));
expectedFullParams.put("three", Collections.singletonList("3"));
expectedFullParams.put("four", Collections.singletonList(""));
expectedFullParams.put("five", Collections.singletonList("4/5&6+7"));
assertEquals(expectedFullParams, fullParams.getParameters());
}

@Test
public void complexParametersToQueryString() {
QueryParameters fullParams = QueryParameters
Expand Down Expand Up @@ -168,22 +198,22 @@ public void underlyingListsUnmodifiable_full() {
@Test
public void parameterWithoutValue() {
QueryParameters params = new QueryParameters(
Collections.singletonMap("foo", Collections.emptyList()));
Collections.singletonMap("foo", Collections.singletonList("")));
Assert.assertEquals("foo", params.getQueryString());

params = new QueryParameters(
Collections.singletonMap("foo", Arrays.asList(null, "bar")));
Collections.singletonMap("foo", Arrays.asList("", "bar")));
Assert.assertEquals("foo&foo=bar", params.getQueryString());

params = new QueryParameters(
Collections.singletonMap("foo", Arrays.asList("bar", null)));
Collections.singletonMap("foo", Arrays.asList("bar", "")));
Assert.assertEquals("foo=bar&foo", params.getQueryString());
}

@Test
public void parameterWithEmptyValue() {
QueryParameters fullParams = new QueryParameters(
Collections.singletonMap("foo", Collections.singletonList("")));
Assert.assertEquals("foo=", fullParams.getQueryString());
Assert.assertEquals("foo", fullParams.getQueryString());
}
}

0 comments on commit a868f2f

Please sign in to comment.