I am using Apollo Kotlin 4.3.1, and I'm not sure i...
# apollo-kotlin
f
I am using Apollo Kotlin 4.3.1, and I'm not sure if this is intentional behavior or a bug, but here's the issue I'm running into (see 🧵): tl;dr server is violating schema nullability, still returns partial data and errors which are discarded by Apollo Kotlin in favor of returning an exception.
Given the following example query (some names have been changed):
Copy code
query test {
  exampleField(first: 15) {
    edges {
      node {
        content {
          id
        }
      }
    }
  }
}
On the schema, the
content
field inside
node
is marked as non-nullable:
Copy code
type Node {
  content: Content!
  # ... other fields
}
When I run the query inside the IDE, I get back the following:
Copy code
{
  "data": {
    "exampleField": {
      "edges": [
        {
          "node": null
        },
        {
          "node": {
            "content": {
              "id": "some-id-1"
            }
          }
        },
        {
          "node": {
            "content": {
              "id": "some-id-2"
            }
          }
        }
      ]
    }
  },
  "errors": [
    {
      "message": "Cannot return null for non-nullable field",
      "path": [
        "exampleField",
        "edges",
        0,
        "node",
        "content"
      ]
    }
  ],
  "extensions": {
    "valueCompletion": [
      {
        "message": "Cannot return null for non-nullable field Node.content",
        "path": [
          "exampleField",
          "edges",
          0,
          "node",
          "content"
        ]
      }
    ]
  }
}
which obviously means that the server is breaking the contract defined by the schema and returning null data, where it should be non-null. I would expect to still see Apollo Kotlin return the partial data and the errors, under
response.data
and
response.errors
respectively, but they are both null. Instead, under
response.exception
I get back an
ApolloNetworkException: Error while reading JSON response
with the underlying
platformCause
being `IllegalStateException: Failed to buffer json reader, expected
BEGIN_OBJECT
but found
NULL
json token`. Here's an example of some code that I have for handling Apollo responses:
Copy code
when {
    response.hasErrors() -> {
        LogEntry(
            apolloOperation = response.operation.toString(),
            message = errorMsg(response),
        ).also(log::e)

        // Continue to send up a response, to account for partial data
        Resource.Response(transform(response))
    }

    exception != null -> logAndReturnFailure(response, exception)
    else -> Resource.Response(transform(response))
}
My expectation is that Apollo would be able to handle the unexpected null, and still return the partial data in the errors, hitting the first case above. Instead, it's hitting the second case, since there are no errors or partial data, but an exception instead. Are there any configuration options, etc. that I can enable to work around this issue and get back the data and errors? Or is there some extra handling that can be added to Apollo Kotlin for cases like this where the server is violating schema nullability?
m
What is the type of
Edge.node
above?
f
It's also non-nullable
Copy code
type Edge {
  node: Node!

  cursor: String!
}
m
Right. So that's the issue
This is not compliant. Your server should bubble the error to the closest nullable parent
f
Thanks, I'll take this back to the engineer in charge of the API. I'm guessing that since the server is being non-compliant that, realistically, there's nothing that the client can do to work around this error in the meantime?
m
There are tools to deal with nullability there: https://www.apollographql.com/docs/kotlin/advanced/nullability They were not really designed to workaround server issues like this but it might help.
I'm in the middle of something right now but can double check a bit later
f
Awesome, I can play around with that in the meantime. Appreciate your help!
m
Seems to work with
@catch
:
Copy code
extend schema @link(
    url: "<https://specs.apollo.dev/nullability/v0.4>",
    import: ["@catch", "CatchTo", "@catchByDefault"]
)

extend schema @catchByDefault(to: NULL)

type Query {
    foo: Foo!
}

type Foo {
    bar: Int!
}
query:
Copy code
query GetFoo {
    foo @catch(to: NULL) {
#       ^ This is the IMPORTANT part
        bar
    }
}
test:
Copy code
// language=JSON
    val json = """
      {
        "foo": null
      }
    """.trimIndent()

    val data = Buffer().writeUtf8(json).jsonReader()
      .let {
        GetFooQuery().parseData(it)
      }

    assertEquals(null, data!!.foo)
even if foo is non-nullable,
@catch
will recover from it
👍 1