I have a question regarding nullable items, specif...
# graphql-kotlin
m
I have a question regarding nullable items, specifically interested in functions which return completable futures. At this point it seems every time a given function returns a nullable value (or nullable type of CF), when the dependency fails, the query returns null and then details in the error field in the response. Failures for the non-nullable types fail the whole query. I guess this works for most of the cases, but what if I need to fail the query completely in the case it given function return a nullable object? Pretty much need to differentiate between a regular value (which can be nullable) and the actual failure -> fail the whole query and not return null). Is something like this doable?
d
If there is a critical failure then just throw exception
If you need to return partial data then return data fetcher result with both data and exception
m
Is it possible to specify this behavior in the CF itself? Or do I need to write a custom DataFetcher?
d
why do you need to work with completable future?
*and yes you should be able to throw exceptions from CF
m
I'm using DataLoaders to load most of the data to the graph. I created a couple of helpers which allow me to easily bridge those from Reactor's Mono streams (using SpringBoot/Webflux as the base framework - historical reasons).
However when I return a failed CF, the query returns null and the error is in the error section. Would like to fail the whole query in such a case.
d
apologies but still not sure what you are trying to do
m
E.g.
Copy code
fun failNullableCF(): CompletableFuture<Boolean?> = CompletableFuture.failedFuture(RuntimeException("failed nullable future"))
    fun failNonNullableCF(): CompletableFuture<Boolean> = CompletableFuture.failedFuture(RuntimeException("failed non nullable future"))
failNullableCF
ends up with
Copy code
{
  "data": {
    "dog": {
      "failNullableCF": null
    }
  },
  "errors": [
    {
      "message": "failed nullable future",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "dog",
        "failNullableCF"
      ]
    }
  ]
}
I would expect this failure to fail the whole response.
Like this:
Copy code
{
  "errors": [
    {
      "message": "failed non nullable future",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "dog",
        "failNonNullableCF"
      ]
    }
  ]
}
(the first query is
Copy code
{ 
  dog {
    failNullableCF
  }
}
and the second query is
Copy code
{ 
  dog {
    failNonNullableCF
  }
}
d
if you attempt to return a null value for a non-nullable field -> it is considered a critical failure so it will bubble up until the top field (hence just the errros block)
m
Right, however semantically a failed CF is not null
d
in case of a nullable field -> null is a valid value and GraphQL supports partial data
m
it is a CF completed exceptionally
d
you would have to dig into the
graphql-java
logic to see how it resolves the CF failures
sounds like it sees no value => defaults to null and adds the exception from CF failure so treats it as a partial
m
BTW I can see a similar behavior in this case though:
Copy code
fun failNullableFun(): Boolean? = throw RuntimeException("failed nullable function")
    fun failNonNullableFun(): Boolean = throw RuntimeException("failed non nullable function")
Copy code
{ 
  dog {
    failNullableFun
  }
}
returns as
Copy code
{
  "data": {
    "dog": {
      "failNullableFun": null
    }
  },
  "errors": [
    {
      "message": "failed nullable function",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "dog",
        "failNullableFun"
      ]
    }
  ]
}
I would expect the query to completely fail
d
well each field is resolved independently
so dog was resolved
but subsequent field failed (but since it is nullable) it is fine
if you want to fail whole query you would need to resolve complete object in the top level query (i.e. in
dog
)
m
right - what you say makes sense to some extent, but semantically
failNullableFun
failed, it did not resolve
d
graphql supports partial data
m
in other words - there is no difference between
Copy code
fun failNullableFun(): Boolean? = throw RuntimeException("failed nullable function")
and
Copy code
fun nullableFun(): Boolean? = null
when resolving the actual values.
d
well there is a diff -> in first case you will get errors populated
m
True there is a diff, however this makes client's life more difficult as they will need to check if there is an error in the case they got null for a value
hence going back to my original question - is it possible to fail the whole query in the case given node is nullable and we fail when obtaining its value
d
you can take a look at the
DataFetcherExceptionHandler
that is provided to the execution strategies
unsure though why you would want to change this behavior
m
Thanks for the pointers. As I wrote, when we fail obtaining a nullable field, it should be still considered as a failure as there is a difference between
null
as a properly loaded value and a failure. One could argue if we care a lot about preserving
null
as a valid value we should use a wrapper, something like
Optional
to express this semantic, and I agree this would be perhaps the best approach. However since GraphQL does not support generic types, this would require creating a union type for each combination, and that does not scale well. Pretty much exploring what other options we have and what would be the best pattern to use to support cases when we can partially fail vs we need completely fail.
Anyway thanks for all the pointers here, will explore more 🙂
s
I think you may need to wrap the result here and have logic to handle the errors of the completable future
CompletableFuture<DatafetcherResult<T>>
m
Thanks, will try something out