-
Notifications
You must be signed in to change notification settings - Fork 683
Description
Description
For previous episodes on error handling, visit #4711
Goal
Simplify the error handling which is currently based on 3 different properties of ApolloResponse:
response.datafor GraphQL dataresponse.errorsfor GraphQL errorsresponse.exceptionfor network exceptions
This is confusing and ideally, we want ApolloResponse to be a result-like class with either data or an exception. There is a lot of brain muscle around result-like classes. Kotlin stdlib has Result, Arrow has Either, Rust has Result, etc... So it feels like a natural fit here.
Proposal: @catch client directive
Inspired by Relay error handling, introduce a @catch client directive that allows user to opt-in error handling on a given field.
If this field or any sub-field contains an entry in the errors array then this error is exposed to the user and no data is shown for that field.
Advantages: data contains everything needed to display the UI. There's no need to go back to the errors array because the error is now inline at the position where it happens. We could keep the errors arrays for advanced use cases but in the very large majority of use cases, it shouldn't be needed. We can rely on data or exception again.
A @catch field is modeled in generated models as a sealed interface and matching result types:
Schema:
type Query {
product: Product!
}
type Product {
name: String!
price: Float!
}Query:
{
product @catch {
name
price
}
}Kotlin codegen:
class Data(val product: ProductResult)
sealed interface ProductResult
class ProductSuccess(val product: Product): ProductResult
class ProductError(val error: Error): ProductResult
class Product(val name: String, val price: Double)Examples
Product error
Query:
{
product @catch {
name
price
}
}Error Response:
{
"errors": [
{
"message": "Cannot resolve product",
"path": ["product"]
}
],
"data": {
"product": null
}
}Kotlin test:
val response = apolloClient.query(Query()).execute()
val product = response.data.product
check(product is ProductError)
check(product.error.path == listOf("product"))price error
If a nested fails, the error is exposed on the closest enclosing @catch field:
{
"errors": [
{
"message": "Cannot resolve product price",
"path": ["product", "price"]
}
],
"data": {
"product": {
"name": "Champagne",
"price": null
}
}
}Kotlin test:
val response = apolloClient.query(Query()).execute()
val product = response.data.product
check(product is ProductError)
check(product.erro.path == listOf("product"))