Skip to content

Commit dba2249

Browse files
author
Łukasz Paczos
committed
improve RouteOptions query parameter encoding
1 parent 5c34c75 commit dba2249

6 files changed

Lines changed: 123 additions & 160 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Mapbox welcomes participation and contributions from everyone.
44

55
### main
66
- Added `TollCollection#name` field which contains a name of the toll booth/gantry, when available. [#1432](https://github.com/mapbox/mapbox-java/pull/1432)
7+
- Improved `RouteOptions#toUrl` query parameters encoding. This (in between other improvements) adds handling for rarer unescaped characters occurrences (like '&' baked within other string). [#1433](https://github.com/mapbox/mapbox-java/pull/1433)
78

89
### v6.5.0-beta.4 - May 5, 2022
910
- Added `RestStop#name` field which contains a name of the service/rest area, when available. [#1428](https://github.com/mapbox/mapbox-java/pull/1428)

services-directions-models/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java

Lines changed: 81 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@
1919
import com.mapbox.geojson.Point;
2020
import java.io.UnsupportedEncodingException;
2121
import java.net.MalformedURLException;
22-
import java.net.URI;
23-
import java.net.URISyntaxException;
2422
import java.net.URL;
2523
import java.net.URLDecoder;
24+
import java.net.URLEncoder;
2625
import java.util.LinkedHashMap;
2726
import java.util.List;
2827
import java.util.Map;
@@ -232,8 +231,9 @@ public List<Bearing> bearingsList() {
232231
* to differentiate them.
233232
* <p>
234233
* If provided, the list of layers must be the same length as the list of coordinates.
234+
*
235235
* @return a string representing the layers with the ; separator. Each value may be negative
236-
or absent.
236+
* or absent.
237237
*/
238238
@Nullable
239239
public abstract String layers();
@@ -871,7 +871,7 @@ public static RouteOptions fromUrl(@NonNull URL url) {
871871
String baseUrl = url.getProtocol() + "://" + url.getHost();
872872
int port = url.getPort();
873873
if (port != -1) {
874-
baseUrl += ":" + port;
874+
baseUrl += ":" + port;
875875
}
876876
optionsJson.addProperty("baseUrl", baseUrl);
877877

@@ -921,101 +921,43 @@ public URL toUrl(@NonNull String accessToken) {
921921
.append(String.format("?%s=%s", ACCESS_TOKEN_URL_PARAM_NAME, accessToken))
922922
.append(String.format("&geometries=%s", geometries()));
923923

924-
if (alternatives() != null) {
925-
sb.append(String.format("&alternatives=%s", alternatives()));
926-
}
927-
if (overview() != null) {
928-
sb.append(String.format("&overview=%s", overview()));
929-
}
930-
if (radiuses() != null) {
931-
sb.append(String.format("&radiuses=%s", radiuses()));
932-
}
933-
if (steps() != null) {
934-
sb.append(String.format("&steps=%s", steps()));
935-
}
936-
if (avoidManeuverRadius() != null) {
937-
sb.append(String.format("&avoid_maneuver_radius=%s", avoidManeuverRadius()));
938-
}
939-
if (bearings() != null) {
940-
sb.append(String.format("&bearings=%s", bearings()));
941-
}
942-
if (layers() != null) {
943-
sb.append(String.format("&layers=%s", layers()));
944-
}
945-
if (continueStraight() != null) {
946-
sb.append(String.format("&continue_straight=%s", continueStraight()));
947-
}
948-
if (annotations() != null) {
949-
sb.append(String.format("&annotations=%s", annotations()));
950-
}
951-
if (language() != null) {
952-
sb.append(String.format("&language=%s", language()));
953-
}
954-
if (roundaboutExits() != null) {
955-
sb.append(String.format("&roundabout_exits=%s", roundaboutExits()));
956-
}
957-
if (voiceInstructions() != null) {
958-
sb.append(String.format("&voice_instructions=%s", voiceInstructions()));
959-
}
960-
if (bannerInstructions() != null) {
961-
sb.append(String.format("&banner_instructions=%s", bannerInstructions()));
962-
}
963-
if (voiceUnits() != null) {
964-
sb.append(String.format("&voice_units=%s", voiceUnits()));
965-
}
966-
if (exclude() != null) {
967-
sb.append(String.format("&exclude=%s", exclude()));
968-
}
969-
if (include() != null) {
970-
sb.append(String.format("&include=%s", include()));
971-
}
972-
if (approaches() != null) {
973-
sb.append(String.format("&approaches=%s", approaches()));
974-
}
975-
if (waypointIndices() != null) {
976-
sb.append(String.format("&waypoints=%s", waypointIndices()));
977-
}
978-
if (waypointNames() != null) {
979-
sb.append(String.format("&waypoint_names=%s", waypointNames()));
980-
}
981-
if (waypointTargets() != null) {
982-
sb.append(String.format("&waypoint_targets=%s", waypointTargets()));
983-
}
984-
if (enableRefresh() != null) {
985-
sb.append(String.format("&enable_refresh=%s", enableRefresh()));
986-
}
987-
if (walkingSpeed() != null) {
988-
sb.append(String.format("&walking_speed=%s", walkingSpeed()));
989-
}
990-
if (walkwayBias() != null) {
991-
sb.append(String.format("&walkway_bias=%s", walkwayBias()));
992-
}
993-
if (alleyBias() != null) {
994-
sb.append(String.format("&alley_bias=%s", alleyBias()));
995-
}
996-
if (snappingIncludeClosures() != null) {
997-
sb.append(String.format("&snapping_include_closures=%s", snappingIncludeClosures()));
998-
}
999-
if (arriveBy() != null) {
1000-
sb.append(String.format("&arrive_by=%s", arriveBy()));
1001-
}
1002-
if (departAt() != null) {
1003-
sb.append(String.format("&depart_at=%s", departAt()));
1004-
}
1005-
if (maxHeight() != null) {
1006-
sb.append(String.format("&max_height=%s", maxHeight()));
1007-
}
1008-
if (maxWidth() != null) {
1009-
sb.append(String.format("&max_width=%s", maxWidth()));
1010-
}
1011-
if (metadata() != null) {
1012-
sb.append(String.format("&metadata=%s", metadata()));
1013-
}
1014-
if (unrecognized() != null) {
1015-
for (Map.Entry<String, SerializableJsonElement> entry : unrecognized().entrySet()) {
924+
appendQueryParameter(sb, "alternatives", alternatives());
925+
appendQueryParameter(sb, "overview", overview());
926+
appendQueryParameter(sb, "radiuses", radiuses());
927+
appendQueryParameter(sb, "steps", steps());
928+
appendQueryParameter(sb, "avoid_maneuver_radius", avoidManeuverRadius());
929+
appendQueryParameter(sb, "bearings", bearings());
930+
appendQueryParameter(sb, "layers", layers());
931+
appendQueryParameter(sb, "continue_straight", continueStraight());
932+
appendQueryParameter(sb, "annotations", annotations());
933+
appendQueryParameter(sb, "language", language());
934+
appendQueryParameter(sb, "roundabout_exits", roundaboutExits());
935+
appendQueryParameter(sb, "voice_instructions", voiceInstructions());
936+
appendQueryParameter(sb, "banner_instructions", bannerInstructions());
937+
appendQueryParameter(sb, "voice_units", voiceUnits());
938+
appendQueryParameter(sb, "exclude", exclude());
939+
appendQueryParameter(sb, "include", include());
940+
appendQueryParameter(sb, "approaches", approaches());
941+
appendQueryParameter(sb, "waypoints", waypointIndices());
942+
appendQueryParameter(sb, "waypoint_names", waypointNames());
943+
appendQueryParameter(sb, "waypoint_targets", waypointTargets());
944+
appendQueryParameter(sb, "enable_refresh", enableRefresh());
945+
appendQueryParameter(sb, "walking_speed", walkingSpeed());
946+
appendQueryParameter(sb, "walkway_bias", walkwayBias());
947+
appendQueryParameter(sb, "alley_bias", alleyBias());
948+
appendQueryParameter(sb, "snapping_include_closures", snappingIncludeClosures());
949+
appendQueryParameter(sb, "arrive_by", arriveBy());
950+
appendQueryParameter(sb, "depart_at", departAt());
951+
appendQueryParameter(sb, "max_height", maxHeight());
952+
appendQueryParameter(sb, "max_width", maxWidth());
953+
appendQueryParameter(sb, "metadata", metadata());
954+
955+
Map<String, SerializableJsonElement> unrecognized = unrecognized();
956+
if (unrecognized != null) {
957+
for (Map.Entry<String, SerializableJsonElement> entry : unrecognized.entrySet()) {
1016958
JsonElement element = entry.getValue().getElement();
1017959
if (element.isJsonPrimitive()) {
1018-
sb.append(String.format("&%s=%s", entry.getKey(), element.getAsString()));
960+
appendQueryParameter(sb, entry.getKey(), element.getAsString());
1019961
} else {
1020962
throw new IllegalStateException(
1021963
String.format(
@@ -1029,20 +971,47 @@ public URL toUrl(@NonNull String accessToken) {
1029971
}
1030972

1031973
try {
1032-
URL decodedUrl = new URL(sb.toString());
1033-
URI encodedUri = new URI(
1034-
decodedUrl.getProtocol(),
1035-
decodedUrl.getUserInfo(),
1036-
decodedUrl.getHost(),
1037-
decodedUrl.getPort(),
1038-
decodedUrl.getPath(),
1039-
decodedUrl.getQuery(),
1040-
decodedUrl.getRef()
1041-
);
1042-
// workaround to encode `&` chars in Strings like "me & you".
1043-
// need a better solution to fix encoding in general.
1044-
return new URL(encodedUri.toASCIIString().replace("%20&%20", "%20%26%20"));
1045-
} catch (MalformedURLException | URISyntaxException ex) {
974+
return new URL(sb.toString());
975+
} catch (MalformedURLException ex) {
976+
throw new RuntimeException(ex);
977+
}
978+
}
979+
980+
private static void appendQueryParameter(
981+
@NonNull StringBuilder builder,
982+
@NonNull String name,
983+
@Nullable Number value) {
984+
if (value != null) {
985+
appendQueryParameter(builder, name, String.valueOf(value));
986+
}
987+
}
988+
989+
private static void appendQueryParameter(
990+
@NonNull StringBuilder builder,
991+
@NonNull String name,
992+
@Nullable Boolean value) {
993+
if (value != null) {
994+
appendQueryParameter(builder, name, String.valueOf(value));
995+
}
996+
}
997+
998+
private static void appendQueryParameter(
999+
@NonNull StringBuilder builder,
1000+
@NonNull String name,
1001+
@Nullable String value) {
1002+
if (value != null) {
1003+
builder
1004+
.append("&")
1005+
.append(name)
1006+
.append("=")
1007+
.append(escape(value));
1008+
}
1009+
}
1010+
1011+
private static String escape(@NonNull String value) {
1012+
try {
1013+
return URLEncoder.encode(value, UTF_8);
1014+
} catch (UnsupportedEncodingException ex) {
10461015
throw new RuntimeException(ex);
10471016
}
10481017
}
@@ -1945,6 +1914,7 @@ public Builder snappingIncludeClosuresList(@Nullable List<Boolean> snappingClosu
19451914
* which are not present in the model yet but are supported on the Directions API,
19461915
* to a URL generated by `RouteOptions#toUrl`.
19471916
* Use it for experimental parameters.
1917+
*
19481918
* @param unrecognizedProperties parameters to add to request
19491919
*/
19501920
@NonNull

0 commit comments

Comments
 (0)