Skip to content

Commit 7c64c04

Browse files
author
Łukasz Paczos
committed
make the access token non-nullable in RouteOptions
This refactors the access token in the RouteOptions to be non-nullable again by first deserializing the json into a generic data structure, appending the provided token, and only then transforming into the type-safe RouteOptions object.
1 parent 169d0e0 commit 7c64c04

3 files changed

Lines changed: 162 additions & 45 deletions

File tree

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

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.google.auto.value.AutoValue;
66
import com.google.gson.Gson;
77
import com.google.gson.GsonBuilder;
8+
import com.google.gson.JsonObject;
89
import com.google.gson.TypeAdapter;
910
import com.google.gson.annotations.SerializedName;
1011
import com.mapbox.api.directions.v5.DirectionsAdapterFactory;
@@ -165,26 +166,66 @@ public static TypeAdapter<DirectionsRoute> typeAdapter(Gson gson) {
165166

166167
/**
167168
* Create a new instance of this class by passing in a formatted valid JSON String.
169+
* <p>
170+
* Use this method if the provided serialized route was not obtained by this library.
171+
* Alternatively, use {@link #fromJson(String, String)}.
172+
* <p>
173+
* If you're using the provided route with the Mapbox Navigation SDK, also see
174+
* {@link #fromJson(String, RouteOptions, String)}.
168175
*
169176
* @param json a formatted valid JSON string defining a GeoJson Directions Route
170177
* @return a new instance of this class defined by the values passed inside this static factory
171178
* method
172-
* @see #fromJson(String, RouteOptions, String)
173-
* @since 3.0.0
174179
*/
175180
public static DirectionsRoute fromJson(@NonNull String json) {
176181
GsonBuilder gson = new GsonBuilder();
182+
JsonObject jsonObject = gson.create().fromJson(json, JsonObject.class);
183+
if (jsonObject.has("routeOptions")) {
184+
throw new IllegalArgumentException(
185+
"Provided serialized route contains RouteOptions. "
186+
+ "Use DirectionsRoute#fromJson(json, accessToken) instead."
187+
);
188+
}
189+
gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create());
190+
gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter());
191+
return gson.create().fromJson(jsonObject, DirectionsRoute.class);
192+
}
193+
194+
/**
195+
* Create a new instance of this class by passing in a formatted valid JSON String.
196+
* <p>
197+
* Use this method if the provided serialized route was obtained by this library.
198+
* This means that it includes {@link RouteOptions} and you need to supply a Mapbox Access Token.
199+
* Alternatively, use {@link #fromJson(String)}.
200+
*
201+
* @param json a formatted valid JSON string defining a GeoJson Directions Route
202+
* @param accessToken a Mapbox Access Token
203+
* @return a new instance of this class defined by the values passed inside this static factory
204+
* method
205+
*/
206+
public static DirectionsRoute fromJson(@NonNull String json, @NonNull String accessToken) {
207+
GsonBuilder gson = new GsonBuilder();
208+
JsonObject jsonObject = gson.create().fromJson(json, JsonObject.class);
209+
if (jsonObject.has("routeOptions")) {
210+
JsonObject routeOptions = jsonObject.getAsJsonObject("routeOptions");
211+
routeOptions.addProperty("access_token", accessToken);
212+
} else {
213+
throw new IllegalArgumentException(
214+
"Provided serialized route does not contain RouteOptions. "
215+
+ "Use DirectionsRoute#fromJson(json) instead."
216+
);
217+
}
177218
gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create());
178219
gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter());
179-
return gson.create().fromJson(json, DirectionsRoute.class);
220+
return gson.create().fromJson(jsonObject, DirectionsRoute.class);
180221
}
181222

182223
/**
183224
* Create a new instance of this class by passing in a formatted valid JSON String.
184225
* <p>
185226
* The parameters of {@link RouteOptions} that were used to make the original route request
186227
* as well as the {@link String} UUID of the original response are needed
187-
* by the Navigation SDK to support correct rerouting and route refreshing.
228+
* by the Mapbox Navigation SDK to support correct rerouting and route refreshing.
188229
*
189230
* @param json a formatted valid JSON string defining a GeoJson Directions Route
190231
* @param routeOptions options that were used during the original route request
@@ -198,10 +239,7 @@ public static DirectionsRoute fromJson(@NonNull String json) {
198239
public static DirectionsRoute fromJson(
199240
@NonNull String json, @Nullable RouteOptions routeOptions, @Nullable String requestUuid
200241
) {
201-
GsonBuilder gson = new GsonBuilder();
202-
gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create());
203-
gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter());
204-
return gson.create().fromJson(json, DirectionsRoute.class)
242+
return fromJson(json)
205243
.toBuilder()
206244
.routeOptions(routeOptions)
207245
.requestUuid(requestUuid)
@@ -223,6 +261,7 @@ public abstract static class Builder {
223261
* @return this builder for chaining options together
224262
* @since 3.0.0
225263
*/
264+
@NonNull
226265
public abstract Builder distance(@NonNull Double distance);
227266

228267
/**
@@ -232,6 +271,7 @@ public abstract static class Builder {
232271
* @return this builder for chaining options together
233272
* @since 3.0.0
234273
*/
274+
@NonNull
235275
public abstract Builder duration(@NonNull Double duration);
236276

237277
/**
@@ -244,6 +284,7 @@ public abstract static class Builder {
244284
* @return this builder for chaining options together
245285
* @since 5.5.0
246286
*/
287+
@NonNull
247288
public abstract Builder durationTypical(@Nullable Double durationTypical);
248289

249290
/**
@@ -253,6 +294,7 @@ public abstract static class Builder {
253294
* @return this builder for chaining options together
254295
* @since 3.0.0
255296
*/
297+
@NonNull
256298
public abstract Builder geometry(@Nullable String geometry);
257299

258300
/**
@@ -262,6 +304,7 @@ public abstract static class Builder {
262304
* @return this builder for chaining options together
263305
* @since 3.0.0
264306
*/
307+
@NonNull
265308
public abstract Builder weight(@Nullable Double weight);
266309

267310
/**
@@ -273,6 +316,7 @@ public abstract static class Builder {
273316
* @return this builder for chaining options together
274317
* @since 3.0.0
275318
*/
319+
@NonNull
276320
public abstract Builder weightName(@Nullable String weightName);
277321

278322
/**
@@ -282,6 +326,7 @@ public abstract static class Builder {
282326
* @return this builder for chaining options together
283327
* @since 3.0.0
284328
*/
329+
@NonNull
285330
public abstract Builder legs(@Nullable List<RouteLeg> legs);
286331

287332
/**
@@ -292,6 +337,7 @@ public abstract static class Builder {
292337
* @return this builder for chaining options together
293338
* @since 3.0.0
294339
*/
340+
@NonNull
295341
public abstract Builder routeOptions(@Nullable RouteOptions routeOptions);
296342

297343
/**
@@ -302,6 +348,7 @@ public abstract static class Builder {
302348
* @return this builder for chaining options together
303349
* @since 3.1.0
304350
*/
351+
@NonNull
305352
public abstract Builder voiceLanguage(@Nullable String voiceLanguage);
306353

307354
/**
@@ -313,6 +360,7 @@ public abstract static class Builder {
313360
@NonNull
314361
public abstract Builder requestUuid(@Nullable String requestUuid);
315362

363+
@NonNull
316364
abstract Builder routeIndex(String routeIndex);
317365

318366
/**
@@ -321,6 +369,7 @@ public abstract static class Builder {
321369
* @return a new {@link DirectionsRoute} using the provided values in this builder
322370
* @since 3.0.0
323371
*/
372+
@NonNull
324373
public abstract DirectionsRoute build();
325374
}
326375
}

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

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.google.auto.value.AutoValue;
77
import com.google.gson.Gson;
88
import com.google.gson.GsonBuilder;
9+
import com.google.gson.JsonElement;
910
import com.google.gson.JsonObject;
1011
import com.google.gson.TypeAdapter;
1112
import com.google.gson.annotations.SerializedName;
@@ -367,15 +368,12 @@ public List<String> annotationsList() {
367368

368369
/**
369370
* A valid Mapbox access token used to making the request.
370-
* <p>
371-
* Avoiding to provide a token will most-likely result in a failure, however,
372-
* it's annotated as nullable to prevent serialization of tokens.
373371
*
374372
* @return a string representing the Mapbox access token
375373
*/
376374
@SerializedName("access_token")
377375
@Ignore(Ignore.Type.SERIALIZATION)
378-
@Nullable
376+
@NonNull
379377
public abstract String accessToken();
380378

381379
/**
@@ -674,42 +672,36 @@ public static TypeAdapter<RouteOptions> typeAdapter(Gson gson) {
674672
}
675673

676674
/**
677-
* Create a new instance of this class by passing in a formatted valid JSON String.
678-
* <p>
679-
* The Mapbox Access Token that was part of the original object was not serialized and needs
680-
* to be provided again.
681-
* The options will not be valid for a request without a Mapbox Access Token.
675+
* Create a new instance of this class by passing in a formatted valid JSON String
676+
* with a Mapbox Access Token.
682677
*
683678
* @param json a formatted valid JSON string defining a RouteOptions
684-
* @param accessToken a Mapbox Access Token
679+
* @param accessToken a Mapbox Access Token since {@link #toJson()} does not serialize the token
685680
* @return a new instance of this class defined by the values passed inside this static factory
686681
* method
687682
* @see #fromUrl(URL)
688683
*/
689684
@NonNull
690-
public static RouteOptions fromJson(@NonNull String json, @Nullable String accessToken) {
691-
return fromJson(json).toBuilder().accessToken(accessToken).build();
685+
public static RouteOptions fromJson(@NonNull String json, @NonNull String accessToken) {
686+
GsonBuilder gson = new GsonBuilder();
687+
688+
JsonObject jsonObject = gson.create().fromJson(json, JsonObject.class);
689+
jsonObject.addProperty("access_token", accessToken);
690+
691+
return fromJsonElement(jsonObject);
692692
}
693693

694694
/**
695-
* Create a new instance of this class by passing in a formatted valid JSON String.
695+
* This takes the currently defined values found inside this instance and converts it to a json
696+
* string.
696697
* <p>
697-
* The Mapbox Access Token that was part of the original object was not serialized and needs
698-
* to be provided again.
699-
* The options will not be valid for a request without a Mapbox Access Token so make sure to
700-
* provide a token with {@link #fromJson(String, String)}
701-
* or rebuild the options with {@link #toBuilder()}.
698+
* The access token field is not serialized when using this method.
702699
*
703-
* @param json a formatted valid JSON string defining a RouteOptions
704-
* @return a new instance of this class defined by the values passed inside this static factory
705-
* method
706-
* @see #fromUrl(URL)
700+
* @return a JSON string
707701
*/
708-
@NonNull
709-
public static RouteOptions fromJson(@NonNull String json) {
710-
GsonBuilder gson = new GsonBuilder();
711-
gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create());
712-
return gson.create().fromJson(json, RouteOptions.class);
702+
@Override
703+
public String toJson() {
704+
return super.toJson();
713705
}
714706

715707
/**
@@ -744,7 +736,21 @@ public static RouteOptions fromUrl(@NonNull URL url) {
744736
}
745737
}
746738

747-
return fromJson(optionsJson.toString());
739+
return fromJsonString(optionsJson.toString());
740+
}
741+
742+
@NonNull
743+
private static RouteOptions fromJsonString(@NonNull String json) {
744+
GsonBuilder gson = new GsonBuilder();
745+
gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create());
746+
return gson.create().fromJson(json, RouteOptions.class);
747+
}
748+
749+
@NonNull
750+
private static RouteOptions fromJsonElement(@NonNull JsonElement jsonElement) {
751+
GsonBuilder gson = new GsonBuilder();
752+
gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create());
753+
return gson.create().fromJson(jsonElement, RouteOptions.class);
748754
}
749755

750756
/**

services-directions-models/src/test/java/com/mapbox/api/directions/v5/models/DirectionsRouteTest.java

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
package com.mapbox.api.directions.v5.models;
22

3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertNull;
36
import com.mapbox.api.directions.v5.DirectionsCriteria;
47
import com.mapbox.core.TestUtils;
58
import com.mapbox.geojson.Point;
6-
7-
import org.junit.Test;
8-
99
import java.util.ArrayList;
10-
11-
import static org.junit.Assert.assertEquals;
12-
import static org.junit.Assert.assertNotNull;
13-
import static org.junit.Assert.assertNull;
10+
import org.hamcrest.junit.ExpectedException;
11+
import org.junit.Rule;
12+
import org.junit.Test;
1413

1514
public class DirectionsRouteTest extends TestUtils {
1615

17-
private static final String DIRECTIONS_V5_VOICE_BANNER_FIXTURE = "directions_v5_voice_banner.json";
18-
private static final String DIRECTIONS_V5_VOICE_INVALID_FIXTURE = "directions_v5_voice_invalid.json";
16+
private static final String DIRECTIONS_V5_VOICE_BANNER_FIXTURE =
17+
"directions_v5_voice_banner.json";
18+
private static final String DIRECTIONS_V5_VOICE_INVALID_FIXTURE =
19+
"directions_v5_voice_invalid.json";
1920
private static final int FIRST_ROUTE = 0;
2021

22+
@Rule
23+
public ExpectedException thrown = ExpectedException.none();
24+
2125
@Test
2226
public void sanity() throws Exception {
2327
DirectionsRoute route = DirectionsRoute.builder()
@@ -73,4 +77,62 @@ public void directionsRoute_doesContainOptionsAndUuid() throws Exception {
7377
assertEquals(options, route.routeOptions());
7478
assertEquals(uuid, route.requestUuid());
7579
}
80+
81+
@Test
82+
public void directionsRoute_json_withOptionsAndUUID_roundTripping() throws Exception {
83+
String json = loadJsonFixture("directions_v5-with-closure_precision_6.json");
84+
RouteOptions options = RouteOptions.builder()
85+
.profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
86+
.coordinatesList(new ArrayList<Point>() {{
87+
add(Point.fromLngLat(1.0, 1.0));
88+
add(Point.fromLngLat(2.0, 2.0));
89+
}})
90+
.accessToken("token")
91+
.build();
92+
String uuid = "123";
93+
DirectionsRoute route = DirectionsRoute.fromJson(json, options, uuid);
94+
95+
String newRouteJson = route.toJson();
96+
97+
DirectionsRoute newRoute = DirectionsRoute.fromJson(newRouteJson, "token");
98+
99+
assertEquals(route, newRoute);
100+
}
101+
102+
@Test
103+
public void directionsRoute_json_invalid_with_options() throws Exception {
104+
thrown.expect(RuntimeException.class);
105+
thrown.expectMessage(
106+
"Provided serialized route contains RouteOptions. "
107+
+ "Use DirectionsRoute#fromJson(json, accessToken) instead.");
108+
String json = loadJsonFixture("directions_v5-with-closure_precision_6.json");
109+
RouteOptions options = RouteOptions.builder()
110+
.profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
111+
.coordinatesList(new ArrayList<Point>() {{
112+
add(Point.fromLngLat(1.0, 1.0));
113+
add(Point.fromLngLat(2.0, 2.0));
114+
}})
115+
.accessToken("token")
116+
.build();
117+
String uuid = "123";
118+
DirectionsRoute route = DirectionsRoute.fromJson(json, options, uuid);
119+
120+
String newRouteJson = route.toJson();
121+
122+
DirectionsRoute.fromJson(newRouteJson);
123+
}
124+
125+
@Test
126+
public void directionsRoute_json_invalid_without_options() throws Exception {
127+
thrown.expect(RuntimeException.class);
128+
thrown.expectMessage(
129+
"Provided serialized route does not contain RouteOptions. "
130+
+ "Use DirectionsRoute#fromJson(json) instead.");
131+
String json = loadJsonFixture("directions_v5-with-closure_precision_6.json");
132+
DirectionsRoute route = DirectionsRoute.fromJson(json);
133+
134+
String newRouteJson = route.toJson();
135+
136+
DirectionsRoute.fromJson(newRouteJson, "token");
137+
}
76138
}

0 commit comments

Comments
 (0)