Description
Version
4.0.0-beta.6
Summary
Enums defined in the provided graphQL schema are defined as pseudo-enums, actually classes wrapping a String member called rawValue
. While it's possible to use the provided enum constants from the class, you can also create your own, possibly passing in null
.
Example enum in the graphql schema:
enum StatusEnum {
GOOD
BAD
}
Generated StatusEnum
class:
public class StatusEnum {
public static EnumType type = new EnumType("StatusEnum", Arrays.asList("GOOD", "BAD"));
public static StatusEnum GOOD = new StatusEnum("GOOD");
public static StatusEnum BAD = new StatusEnum("BAD");
public String rawValue;
public StatusEnum(String rawValue) {
this.rawValue = rawValue;
}
public static StatusEnum safeValueOf(String rawValue) {
switch(rawValue) {
case "GOOD": return StatusEnum.GOOD;
case "BAD": return StatusEnum.BAD;
default: return new StatusEnum.UNKNOWN__(rawValue);
}
}
// ... lots of other stuff
}
A good usage of StatusEnum would be to use the constants such as StatusEnum.GOOD
, or StatusEnum.BAD
. Another valid use of StatusEnum
would be to parse the string "GOOD"
into StatusEnum.GOOD
using StatusEnum.safeValueOf("GOOD")
or even new StatusEnum("GOOD")
.
An invalid, yet allowed, usage of StatusEnum is to pass in null
to the constructor: new StatusEnum(null)
. This isn't checked for invalidity at any point, even up through making the GraphQL call:
var query = GetThingQuery.builder()
// !!! Set the input enum to null !!!
.something(new StatusEnum(null))
.build()
// hangs forever!!
var result = Rx3Apollo.single(apolloClient.query(query), BackpressureStrategy.BUFFER).blockingGet();
When used in conjunction with Rx3Apollo.single(...).blockingGet()
, a NullPointerException is thrown in another thread when attempting to serialize this value:
Exception in thread "Apollo Dispatcher" java.lang.NullPointerException: Parameter specified as non-null is null: method com.apollographql.apollo3.api.json.internal.FileUploadAwareJsonWriter.value, parameter value
at com.apollographql.apollo3.api.json.internal.FileUploadAwareJsonWriter.value(FileUploadAwareJsonWriter.kt)
at com.apollographql.apollo3.api.json.internal.FileUploadAwareJsonWriter.value(FileUploadAwareJsonWriter.kt:11)
at org.example.type.adapter.StatusEnum_ResponseAdapter.toJson(StatusEnum_ResponseAdapter.java:29)
at org.example.type.adapter.StatusEnum_ResponseAdapter.toJson(StatusEnum_ResponseAdapter.java:16)
at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:66)
at com.apollographql.apollo3.api.ApolloOptionalAdapter.toJson(Adapters.kt:114)
at org.example.adapter.GetThingQuery_VariablesAdapter.serializeVariables(GetThingQuery_VariablesAdapter.java:27)
at org.example.GetThingQuery.serializeVariables(GetThingQuery.java:110)
at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.composePostParams(DefaultHttpRequestComposer.kt:126)
at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.access$composePostParams(DefaultHttpRequestComposer.kt:77)
at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.buildPostBody(DefaultHttpRequestComposer.kt:225)
at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer.compose(DefaultHttpRequestComposer.kt:71)
at com.apollographql.apollo3.runtime.java.network.http.HttpNetworkTransport.execute(HttpNetworkTransport.java:49)
at com.apollographql.apollo3.runtime.java.ApolloClient$NetworkInterceptor.intercept(ApolloClient.java:147)
at com.apollographql.apollo3.runtime.java.interceptor.internal.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:35)
at com.apollographql.apollo3.runtime.java.ApolloClient.lambda$execute$0(ApolloClient.java:131)
Because the serialization exception happens in a different thread, but we're using Rx3Apollo.single(...).blockingGet()
to block until the request finishes, the result is that the current thread hangs forever.
Obviously, this is an invalid use of the generated enum -- or maybe it's valid and null
should be the resulting serialized value. In any case, it would be much more useful to have the library do something (either use null
or surface the exception to the calling thread) rather than just hang forever.
Possible Solutions
- Don't allow construction of generated enums with null values - this would be nice since it would catch this particular issue at the earliest possible spot. Just a simple
if (rawValue == null) throw new NullPointerException()
in the constructor would suffice. - Check during serialization whether
rawValue
is null, and if so, serialize to JSON asnull
-- this is in caserawValue=null
is a valid state and should be allowed. - Surface the NullPointerException up to the calling thread like any other exception might be discovered -- this is slightly less preferred, but maybe this should be happening anyway for serialization failures?
Just some ideas. I have never contributed to this library and TBH I'm still figuring out how best to use it. Please let me know the desired path forward.
Steps to reproduce the behavior
I've created a repo to reproduce this behavior in the simplest possible way: https://github.com/japhib/apollo-null-enum-test
To reproduce the bug, simply run ./gradlew test
and observe that the test TestMain.testNullEnumHangs()
times out after the configured 30 second timeout.
Key parts:
- A GraphQL schema, with both an enum and a query that takes that enum as an input variable:
# schema.graphqls
type Query {
everything(something: StatusEnum): TypeA
}
type TypeA {
status: String
}
enum StatusEnum {
GOOD
BAD
}
# GetThing.graphql
query GetThing($something: StatusEnum) {
everything(something: $something) {
status
__typename
}
}
- The plugin to generate Java code based on that schema
# build.gradle
plugins {
id("com.apollographql.apollo3") version "4.0.0-beta.6"
}
- Create the query with an instance of the enum, but passing in
null
as therawValue
for the enum constructor:
var query = GetThingQuery.builder()
// !!! Set the rawValue of the enum to null !!!
.something(new StatusEnum(null))
.build()
- Use Rx3Apollo to make the call using the specified query:
// hangs forever!!
var result = Rx3Apollo.single(apolloClient.query(query), BackpressureStrategy.BUFFER).blockingGet();
Logs
See above.
thanks!
Just wanted to say, thanks for this library! For the most part it has been great to use and has solved the problem of using GraphQL from Java in a rather elegant way. The code generation is nice to work with and the IntelliJ plugin (maybe maintained by this org?) is also very convenient to work with.