https://kotlinlang.org logo
Title
r

rocketraman

07/21/2021, 1:49 PM
I have a custom scalar type of
ClosedRange<LocalDate>
. I have defined and registered a
Coercing<ClosedRange<LocalDate>, String>
(and via debugger confirmed the coercer is being called at runtime), but I get a Jackson exception when parsing an Input:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `kotlin.ranges.ClosedRange` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: my.MyInput["range"])
Any idea why that would be happening?
d

Dariusz Kuc

07/21/2021, 1:58 PM
how did you define your
ClosedRange<LocalDate>
? Jackson requires default (empty) constructor or you will have to annotate your data class
r

rocketraman

07/21/2021, 1:58 PM
ClosedRange
is a Kotlin stdlib type.
Maybe I misunderstand something about the interaction between coercers and Jackson. I thought if I defined a coercer then Jackson would not be invoked to instantiate that type, as my coercer instantiates it.
Could it have something to do with the fact that
ClosedRange
is an interface type? The underlying type is
ComparableRange
(private to stdlib).
d

Dariusz Kuc

07/21/2021, 2:10 PM
where was it thrown? (from stacktrace)
r

rocketraman

07/21/2021, 2:10 PM
at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4236) ~[jackson-databind-2.11.4.jar:2.11.4]
	at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:4167) ~[jackson-databind-2.11.4.jar:2.11.4]
	at com.expediagroup.graphql.generator.execution.FunctionDataFetcher.convertValue(FunctionDataFetcher.kt:161) ~[graphql-kotlin-schema-generator-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.generator.execution.FunctionDataFetcher.convertArgumentToObject(FunctionDataFetcher.kt:142) ~[graphql-kotlin-schema-generator-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.generator.execution.FunctionDataFetcher.mapParameterToValue(FunctionDataFetcher.kt:114) ~[graphql-kotlin-schema-generator-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.generator.execution.FunctionDataFetcher.getParameters(FunctionDataFetcher.kt:90) ~[graphql-kotlin-schema-generator-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.generator.execution.FunctionDataFetcher.get(FunctionDataFetcher.kt:69) ~[graphql-kotlin-schema-generator-4.1.1.jar:4.1.1]
	at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:270) ~[graphql-java-16.2.jar:?]
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:203) ~[graphql-java-16.2.jar:?]
	at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:60) ~[graphql-java-16.2.jar:?]
	at graphql.execution.ExecutionStrategy.completeValueForObject(ExecutionStrategy.java:646) ~[graphql-java-16.2.jar:?]
	at graphql.execution.ExecutionStrategy.completeValue(ExecutionStrategy.java:438) ~[graphql-java-16.2.jar:?]
	at graphql.execution.ExecutionStrategy.completeField(ExecutionStrategy.java:390) ~[graphql-java-16.2.jar:?]
	at graphql.execution.ExecutionStrategy.lambda$resolveFieldWithInfo$1(ExecutionStrategy.java:205) ~[graphql-java-16.2.jar:?]
	at java.util.concurrent.CompletableFuture.uniApplyNow(CompletableFuture.java:680) ~[?:?]
	at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:658) ~[?:?]
	at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:2094) ~[?:?]
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:204) ~[graphql-java-16.2.jar:?]
	at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:60) ~[graphql-java-16.2.jar:?]
	at graphql.execution.Execution.executeOperation(Execution.java:165) ~[graphql-java-16.2.jar:?]
	at graphql.execution.Execution.execute(Execution.java:104) ~[graphql-java-16.2.jar:?]
	at graphql.GraphQL.execute(GraphQL.java:557) ~[graphql-java-16.2.jar:?]
	at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:482) ~[graphql-java-16.2.jar:?]
	at graphql.GraphQL.executeAsync(GraphQL.java:446) ~[graphql-java-16.2.jar:?]
	at com.expediagroup.graphql.server.execution.GraphQLRequestHandler.executeRequest$suspendImpl(GraphQLRequestHandler.kt:45) ~[graphql-kotlin-server-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.server.execution.GraphQLRequestHandler.executeRequest(GraphQLRequestHandler.kt) ~[graphql-kotlin-server-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.server.execution.GraphQLServer.execute$suspendImpl(GraphQLServer.kt:48) ~[graphql-kotlin-server-4.1.1.jar:4.1.1]
	at com.expediagroup.graphql.server.execution.GraphQLServer.execute(GraphQLServer.kt) ~[graphql-kotlin-server-4.1.1.jar:4.1.1]
	<my call handler here>
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
	<jackson databind classes here>
r

rocketraman

07/21/2021, 2:15 PM
Implying?
d

Dariusz Kuc

07/21/2021, 2:17 PM
If I remember right* we had to add this logic to correctly cast the input arguments from data fetching environment *it's been a while
r

rocketraman

07/21/2021, 2:19 PM
So what are my options?
d

Dariusz Kuc

07/21/2021, 2:28 PM
you could look into creating custom data fetcher that extends default function data fetcher and override
mapParameterToValue
with custom logic to handle closed range
r

rocketraman

07/21/2021, 2:30 PM
Thx, I'll give it a shot
Hmm, the custom data fetcher operates on the parent type that contains the
ClosedRange
field. So do I need to add special logic for every type where I use
ClosedRange
(yuck)?
d

Dariusz Kuc

07/21/2021, 2:43 PM
based on the above stacktrace issue is when using this custom scalar as input -> this implies that it is a function
for properties default data fetcher should be fine (and values should be automatically coerced)
r

rocketraman

07/21/2021, 2:47 PM
Doesn't seem.
ClosedRange
is a property of my input data class type, and when
mapParameterToValue
is called, the
param
value is the input data class type. Just tried it with the debugger.
d

Dariusz Kuc

07/21/2021, 2:48 PM
yeah if it is a property on the input class that logic there attempts to serialize the whole object
you could try annotating your input data class with custom serializer/converter info that describes how to handle the closed range
r

rocketraman

07/21/2021, 5:27 PM
I tried to create a custom Jackson
StdDeserializer
but this is what seems to be happening: serialized input on the wire -> coercer deserializes into
ClosedRange<LocalDate>
-> something serializes
ClosedRange<LocalDate>
into crazy JSON as the java.time objects have lots of internal state -> custom deserializer runs with crazy JSON
I can certainly extract what I need from the crazy JSON, but this seems really inelegant and inefficient
s

Shane Myrick

07/21/2021, 6:47 PM
You need to create your custom object mapper and make sure that gets passed in and used in the
FunctionDataFetcher
https://github.com/ExpediaGroup/graphql-kotlin/blob/a6b957c999398c741cdfbc955fb862[…]expediagroup/graphql/generator/execution/FunctionDataFetcher.kt As mentioned above, you can customize the how
FunctionDataFetcher
gets created with
KotlinDataFetcherFactoryProvider
r

rocketraman

07/21/2021, 6:48 PM
I tried that @Shane Myrick -- the FunctionDataFetcher gets the whole Input object, never just the
ClosedRange<LocalDate>
property. I'm not going to create custom fetchers for every object that happens to have a property of that type.
I created this issue with a working workaround, but there is some weird multi-level deserialization -> serialization -> deserialization happening: https://github.com/ExpediaGroup/graphql-kotlin/issues/1220.