3
3
import com .bedatadriven .jackson .datatype .jts .JtsModule ;
4
4
import com .fasterxml .jackson .databind .JsonNode ;
5
5
import com .fasterxml .jackson .databind .ObjectMapper ;
6
+ import graphql .language .FloatValue ;
7
+ import graphql .language .IntValue ;
6
8
import graphql .language .StringValue ;
7
9
import graphql .relay .Relay ;
8
10
import graphql .schema .Coercing ;
14
16
import java .time .OffsetDateTime ;
15
17
import java .time .ZonedDateTime ;
16
18
import java .time .format .DateTimeFormatter ;
19
+ import java .util .Optional ;
17
20
import javax .annotation .Nonnull ;
18
21
import org .locationtech .jts .geom .Geometry ;
19
22
import org .opentripplanner .framework .geometry .GeometryUtils ;
20
23
import org .opentripplanner .framework .graphql .scalar .DurationScalarFactory ;
24
+ import org .opentripplanner .framework .model .Cost ;
21
25
import org .opentripplanner .framework .model .Grams ;
22
26
import org .opentripplanner .framework .time .OffsetDateTimeParser ;
23
27
24
28
public class GraphQLScalars {
25
29
26
- private static final ObjectMapper geoJsonMapper = new ObjectMapper ()
30
+ private static final ObjectMapper GEOJSON_MAPPER = new ObjectMapper ()
27
31
.registerModule (new JtsModule (GeometryUtils .getGeometryFactory ()));
28
- public static GraphQLScalarType DURATION_SCALAR = DurationScalarFactory .createDurationScalar ();
32
+ public static final GraphQLScalarType DURATION_SCALAR = DurationScalarFactory .createDurationScalar ();
29
33
30
34
public static final GraphQLScalarType POLYLINE_SCALAR = GraphQLScalarType
31
35
.newScalar ()
@@ -112,6 +116,127 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce
112
116
)
113
117
.build ();
114
118
119
+ public static final GraphQLScalarType COORDINATE_VALUE_SCALAR = GraphQLScalarType
120
+ .newScalar ()
121
+ .name ("CoordinateValue" )
122
+ .coercing (
123
+ new Coercing <Double , Double >() {
124
+ private static final String VALIDATION_ERROR_MESSAGE = "Not a valid WGS84 coordinate value" ;
125
+
126
+ @ Override
127
+ public Double serialize (@ Nonnull Object dataFetcherResult )
128
+ throws CoercingSerializeException {
129
+ if (dataFetcherResult instanceof Double doubleValue ) {
130
+ return doubleValue ;
131
+ } else if (dataFetcherResult instanceof Float floatValue ) {
132
+ return floatValue .doubleValue ();
133
+ } else {
134
+ throw new CoercingSerializeException (
135
+ "Cannot serialize object of class %s as a coordinate number" .formatted (
136
+ dataFetcherResult .getClass ().getSimpleName ()
137
+ )
138
+ );
139
+ }
140
+ }
141
+
142
+ @ Override
143
+ public Double parseValue (Object input ) throws CoercingParseValueException {
144
+ if (input instanceof Double doubleValue ) {
145
+ return validateCoordinate (doubleValue )
146
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
147
+ }
148
+ if (input instanceof Integer intValue ) {
149
+ return validateCoordinate (intValue )
150
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
151
+ }
152
+ throw new CoercingParseValueException (
153
+ "Expected a number, got %s %s" .formatted (input .getClass ().getSimpleName (), input )
154
+ );
155
+ }
156
+
157
+ @ Override
158
+ public Double parseLiteral (Object input ) throws CoercingParseLiteralException {
159
+ if (input instanceof FloatValue coordinate ) {
160
+ return validateCoordinate (coordinate .getValue ().doubleValue ())
161
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
162
+ }
163
+ if (input instanceof IntValue coordinate ) {
164
+ return validateCoordinate (coordinate .getValue ().doubleValue ())
165
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
166
+ }
167
+ throw new CoercingParseLiteralException (
168
+ "Expected a number, got: " + input .getClass ().getSimpleName ()
169
+ );
170
+ }
171
+
172
+ private static Optional <Double > validateCoordinate (double coordinate ) {
173
+ if (coordinate >= -180.001 && coordinate <= 180.001 ) {
174
+ return Optional .of (coordinate );
175
+ }
176
+ return Optional .empty ();
177
+ }
178
+ }
179
+ )
180
+ .build ();
181
+
182
+ public static final GraphQLScalarType COST_SCALAR = GraphQLScalarType
183
+ .newScalar ()
184
+ .name ("Cost" )
185
+ .coercing (
186
+ new Coercing <Cost , Integer >() {
187
+ private static final int MAX_COST = 1000000 ;
188
+ private static final String VALIDATION_ERROR_MESSAGE =
189
+ "Cost cannot be negative or greater than %d" .formatted (MAX_COST );
190
+
191
+ @ Override
192
+ public Integer serialize (@ Nonnull Object dataFetcherResult )
193
+ throws CoercingSerializeException {
194
+ if (dataFetcherResult instanceof Integer intValue ) {
195
+ return intValue ;
196
+ } else if (dataFetcherResult instanceof Cost costValue ) {
197
+ return costValue .toSeconds ();
198
+ } else {
199
+ throw new CoercingSerializeException (
200
+ "Cannot serialize object of class %s as a cost" .formatted (
201
+ dataFetcherResult .getClass ().getSimpleName ()
202
+ )
203
+ );
204
+ }
205
+ }
206
+
207
+ @ Override
208
+ public Cost parseValue (Object input ) throws CoercingParseValueException {
209
+ if (input instanceof Integer intValue ) {
210
+ return validateCost (intValue )
211
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
212
+ }
213
+ throw new CoercingParseValueException (
214
+ "Expected an integer, got %s %s" .formatted (input .getClass ().getSimpleName (), input )
215
+ );
216
+ }
217
+
218
+ @ Override
219
+ public Cost parseLiteral (Object input ) throws CoercingParseLiteralException {
220
+ if (input instanceof IntValue intValue ) {
221
+ var value = intValue .getValue ().intValue ();
222
+ return validateCost (value )
223
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
224
+ }
225
+ throw new CoercingParseLiteralException (
226
+ "Expected an integer, got: " + input .getClass ().getSimpleName ()
227
+ );
228
+ }
229
+
230
+ private static Optional <Cost > validateCost (int cost ) {
231
+ if (cost >= 0 && cost <= MAX_COST ) {
232
+ return Optional .of (Cost .costOfSeconds (cost ));
233
+ }
234
+ return Optional .empty ();
235
+ }
236
+ }
237
+ )
238
+ .build ();
239
+
115
240
public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType
116
241
.newScalar ()
117
242
.name ("GeoJson" )
@@ -122,7 +247,7 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce
122
247
public JsonNode serialize (Object dataFetcherResult ) throws CoercingSerializeException {
123
248
if (dataFetcherResult instanceof Geometry ) {
124
249
var geom = (Geometry ) dataFetcherResult ;
125
- return geoJsonMapper .valueToTree (geom );
250
+ return GEOJSON_MAPPER .valueToTree (geom );
126
251
}
127
252
return null ;
128
253
}
@@ -196,20 +321,159 @@ public Double serialize(Object dataFetcherResult) throws CoercingSerializeExcept
196
321
197
322
@ Override
198
323
public Grams parseValue (Object input ) throws CoercingParseValueException {
199
- if (input instanceof Double ) {
200
- var grams = (Double ) input ;
201
- return new Grams (grams );
324
+ if (input instanceof Double doubleValue ) {
325
+ return new Grams (doubleValue );
202
326
}
203
- return null ;
327
+ if (input instanceof Integer intValue ) {
328
+ return new Grams (intValue );
329
+ }
330
+ throw new CoercingParseValueException (
331
+ "Expected a number, got %s %s" .formatted (input .getClass ().getSimpleName (), input )
332
+ );
204
333
}
205
334
206
335
@ Override
207
336
public Grams parseLiteral (Object input ) throws CoercingParseLiteralException {
208
- if (input instanceof Double ) {
209
- var grams = (Double ) input ;
210
- return new Grams (grams );
337
+ if (input instanceof FloatValue coordinate ) {
338
+ return new Grams (coordinate .getValue ().doubleValue ());
211
339
}
212
- return null ;
340
+ if (input instanceof IntValue coordinate ) {
341
+ return new Grams (coordinate .getValue ().doubleValue ());
342
+ }
343
+ throw new CoercingParseLiteralException (
344
+ "Expected a number, got: " + input .getClass ().getSimpleName ()
345
+ );
346
+ }
347
+ }
348
+ )
349
+ .build ();
350
+
351
+ public static final GraphQLScalarType RATIO_SCALAR = GraphQLScalarType
352
+ .newScalar ()
353
+ .name ("Ratio" )
354
+ .coercing (
355
+ new Coercing <Double , Double >() {
356
+ private static final String VALIDATION_ERROR_MESSAGE =
357
+ "Value is under 0 or greater than 1." ;
358
+
359
+ @ Override
360
+ public Double serialize (@ Nonnull Object dataFetcherResult )
361
+ throws CoercingSerializeException {
362
+ var validationException = new CoercingSerializeException (VALIDATION_ERROR_MESSAGE );
363
+ if (dataFetcherResult instanceof Double doubleValue ) {
364
+ return validateRatio (doubleValue ).orElseThrow (() -> validationException );
365
+ } else if (dataFetcherResult instanceof Float floatValue ) {
366
+ return validateRatio (floatValue .doubleValue ()).orElseThrow (() -> validationException );
367
+ } else {
368
+ throw new CoercingSerializeException (
369
+ "Cannot serialize object of class %s as a ratio" .formatted (
370
+ dataFetcherResult .getClass ().getSimpleName ()
371
+ )
372
+ );
373
+ }
374
+ }
375
+
376
+ @ Override
377
+ public Double parseValue (Object input ) throws CoercingParseValueException {
378
+ if (input instanceof Double doubleValue ) {
379
+ return validateRatio (doubleValue )
380
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
381
+ }
382
+ if (input instanceof Integer intValue ) {
383
+ return validateRatio (intValue )
384
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
385
+ }
386
+ throw new CoercingParseValueException (
387
+ "Expected a number, got %s %s" .formatted (input .getClass ().getSimpleName (), input )
388
+ );
389
+ }
390
+
391
+ @ Override
392
+ public Double parseLiteral (Object input ) throws CoercingParseLiteralException {
393
+ if (input instanceof FloatValue ratio ) {
394
+ return validateRatio (ratio .getValue ().doubleValue ())
395
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
396
+ }
397
+ if (input instanceof IntValue ratio ) {
398
+ return validateRatio (ratio .getValue ().doubleValue ())
399
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
400
+ }
401
+ throw new CoercingParseLiteralException (
402
+ "Expected a number, got: " + input .getClass ().getSimpleName ()
403
+ );
404
+ }
405
+
406
+ private static Optional <Double > validateRatio (double ratio ) {
407
+ if (ratio >= -0.001 && ratio <= 1.001 ) {
408
+ return Optional .of (ratio );
409
+ }
410
+ return Optional .empty ();
411
+ }
412
+ }
413
+ )
414
+ .build ();
415
+
416
+ public static final GraphQLScalarType RELUCTANCE_SCALAR = GraphQLScalarType
417
+ .newScalar ()
418
+ .name ("Reluctance" )
419
+ .coercing (
420
+ new Coercing <Double , Double >() {
421
+ private static final double MIN_Reluctance = 0.1 ;
422
+ private static final double MAX_Reluctance = 100000 ;
423
+ private static final String VALIDATION_ERROR_MESSAGE =
424
+ "Reluctance needs to be between %s and %s" .formatted (MIN_Reluctance , MAX_Reluctance );
425
+
426
+ @ Override
427
+ public Double serialize (@ Nonnull Object dataFetcherResult )
428
+ throws CoercingSerializeException {
429
+ if (dataFetcherResult instanceof Double doubleValue ) {
430
+ return doubleValue ;
431
+ } else if (dataFetcherResult instanceof Float floatValue ) {
432
+ return floatValue .doubleValue ();
433
+ } else {
434
+ throw new CoercingSerializeException (
435
+ "Cannot serialize object of class %s as a reluctance" .formatted (
436
+ dataFetcherResult .getClass ().getSimpleName ()
437
+ )
438
+ );
439
+ }
440
+ }
441
+
442
+ @ Override
443
+ public Double parseValue (Object input ) throws CoercingParseValueException {
444
+ if (input instanceof Double doubleValue ) {
445
+ return validateReluctance (doubleValue )
446
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
447
+ }
448
+ if (input instanceof Integer intValue ) {
449
+ return validateReluctance (intValue )
450
+ .orElseThrow (() -> new CoercingParseValueException (VALIDATION_ERROR_MESSAGE ));
451
+ }
452
+ throw new CoercingParseValueException (
453
+ "Expected a number, got %s %s" .formatted (input .getClass ().getSimpleName (), input )
454
+ );
455
+ }
456
+
457
+ @ Override
458
+ public Double parseLiteral (Object input ) throws CoercingParseLiteralException {
459
+ if (input instanceof FloatValue reluctance ) {
460
+ return validateReluctance (reluctance .getValue ().doubleValue ())
461
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
462
+ }
463
+ if (input instanceof IntValue reluctance ) {
464
+ return validateReluctance (reluctance .getValue ().doubleValue ())
465
+ .orElseThrow (() -> new CoercingParseLiteralException (VALIDATION_ERROR_MESSAGE ));
466
+ }
467
+ throw new CoercingParseLiteralException (
468
+ "Expected a number, got: " + input .getClass ().getSimpleName ()
469
+ );
470
+ }
471
+
472
+ private static Optional <Double > validateReluctance (double reluctance ) {
473
+ if (reluctance >= MIN_Reluctance - 0.001 && reluctance <= MAX_Reluctance + 0.001 ) {
474
+ return Optional .of (reluctance );
475
+ }
476
+ return Optional .empty ();
213
477
}
214
478
}
215
479
)
0 commit comments