router
router copied to clipboard
@defer: error related to a field in a deferred fragment appears in chunk for non-deferred fragment
Describe the bug Using the graphql test suite as an inspiration, I am selecting a field that resolves to an error, in a deferred fragment:
query HandlesErrorsThrownInDeferredFragmentsQuery {
computer(id: "Computer1") {
id
...ComputerErrorField @defer
}
}
fragment ComputerErrorField on Computer {
errorField
}
I am receiving the error in the first chunk (along with id) and no errors in the second chunk, whereas I would have expected the opposite (error in the 2nd chunk along errorField), as graphql-js apparently does.
Unsure if this is the spec really mandates this, it may be just a different and acceptable implementation, if so feel free to close!
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"errors":[{"message":"Subgraph errors redacted"}],"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":{"errorField":null},"path":["computer"]}]}
--graphql--
To Reproduce
git clone [email protected]:apollographql/apollo-kotlin.git
cd apollo-kotlin && git checkout defer-with-router-tests
(cd tests/defer/router/subgraphs/computers && npm install && APOLLO_PORT=4001 npm start)&
path/to/router --supergraph tests/defer/router/simple-supergraph.graphqls &
curl --request POST --header 'content-type: application/json' --url http://localhost:4000 --data '{"query":"query HandlesErrorsThrownInDeferredFragmentsQuery { computer(id: \"Computer1\") { id ...ComputerErrorField @defer } } fragment ComputerErrorField on Computer { errorField }"}' --header 'Accept: multipart/mixed; deferSpec=20220824'
It's a bit worse if the field is not nullable: because of bubbling up we never get the id field
actual:
--graphql
content-type: application/json
{"data":{"computer":null},"errors":[{"message":"Subgraph errors redacted"}],"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":null,"path":["computer"]}]}
--graphql--
whereas in theory we should be able to get it in the first chunk.
expected:
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":null,"errors":[{"message":"Subgraph errors redacted"}],"path":["computer"]}]}
--graphql--
Could you add this to the config so we see xhat error is really sent?:
include_subgraph_errors:
all: true
Are all fields returned by a single subgraph request? That might be the reason here: the primary response aggregates errors from its subgraph calls and returns it, then deferred responses do the same from their own subgraph calls, but if they did not call a subgraph and instead return data from the primary's subgraph call, the error is probably not returned.
If that's the case, I think that could be fixed by transmitting to the deferred fragment execution the errors previously generated, then when creating the response, we would filter which errors are supposed to be returned
Yes, in my test I actually have only one subgraph.
Here's the result with the full errors:
Nullable field:
{
"data": {
"computer": {
"id": "Computer1"
}
},
"errors": [
{
"message": "Error field",
"locations": [
{
"line": 1,
"column": 93
}
],
"path": [
"computer",
"errorField"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Error field",
" at Object.errorField (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/computers.js:23:19)",
" at field.resolve (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:56:26)",
" at executeField (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:481:20)",
" at executeFields (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)",
" at completeObjectValue (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:914:10)",
" at completeValue (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:635:12)",
" at executeField (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:489:19)",
" at executeFields (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)",
" at executeOperation (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:344:14)",
" at execute (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:136:20)"
]
}
}
}
]
}
Non nullable field:
{
"data": {
"computer": null
},
"errors": [
{
"message": "Error field",
"locations": [
{
"line": 1,
"column": 93
}
],
"path": [
"computer",
"nonNullErrorField"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Error field",
" at Object.nonNullErrorField (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/computers.js:26:19)",
" at field.resolve (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:56:26)",
" at executeField (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:481:20)",
" at executeFields (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)",
" at completeObjectValue (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:914:10)",
" at completeValue (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:635:12)",
" at executeField (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:489:19)",
" at executeFields (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)",
" at executeOperation (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:344:14)",
" at execute (/Users/bod/gitrepo/apollo-kotlin-1/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:136:20)"
]
}
}
}
]
}
👋 I'm still seeing the same issue with v1.2.1.
To reproduce:
git clone [email protected]:apollographql/apollo-kotlin.git
cd apollo-kotlin
(cd tests/defer/router/subgraphs/computers && npm install && APOLLO_PORT=4001 npm start)&
path/to/router --supergraph tests/defer/router/simple-supergraph.graphqls &
curl --request POST --header 'content-type: application/json' \
--url http://localhost:4000/ \
--data '{"query":"query HandlesErrorsThrownInDeferredFragmentsQuery { computer(id: \"Computer1\") { id ...ComputerErrorField @defer } } fragment ComputerErrorField on Computer { errorField }"}' --header 'Accept: multipart/mixed; deferSpec=20220824'
query:
query HandlesErrorsThrownInDeferredFragmentsQuery {
computer(id: "Computer1") {
id
...ComputerErrorField @defer
}
}
fragment ComputerErrorField on Computer {
errorField
}
Expected:
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":{"errorField":null},"errors":[{"message":"Subgraph errors redacted"}],"path":["computer"]}]}
--graphql--
Actual:
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"errors":[{"message":"Subgraph errors redacted"}],"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":{"errorField":null},"path":["computer"]}]}
--graphql--
I can still reproduce on 1.6.0 with the same steps as above
thank you for testing it, I'll check
I just tested on the branch update-defer-test-with-router-1-6-0
And I get this:
$ curl --request POST --header 'content-type: application/json' --url http://localhost:4000 --data '{"query":"query HandlesErrorsThrownInDeferredFragmentsQuery { computer(id: \"Computer1\
") { id ...ComputerErrorField @defer } } fragment ComputerErrorField on Computer { errorField }"}' --header 'Accept: multipart/mixed; deferSpec=20220824'
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":{"errorField":null},"path":["computer"],"errors":[{"message":"Error field","locations":[{"line":1,"column":93}],"path":["computer","errorField"],"extensions":{"code":"INTERNAL_SERVER_ERROR","excepti
on":{"stacktrace":["Error: Error field"," at Object.errorField (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/computers.js:28:19)"," at field.resolve (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/c
omputers/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:56:26)"," at executeField (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:481:20)"," at exe$
uteFields (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)"," at completeObjectValue (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_module
s/graphql/execution/execute.js:914:10)"," at completeValue (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:635:12)"," at executeField (/home/geal/dev/apollo-kotlin/tests
/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:489:19)"," at executeFields (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)"," at exe
cuteOperation (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:344:14)"," at execute (/home/geal/dev/apollo-kotlin/tests/defer/router/subgraphs/computers/node_modules/graphq
l/execution/execute.js:136:20)"]}}}]}]}
--graphql--
so this looks like the expected result? Are you sure this is using the 1.6 router?
Odd! It does says 1.6.0 when starting it.
Wait the behavior is different with --dev! 🤔
Without:
$ ~/Tmp/router/router --supergraph tests/defer/router/simple-supergraph.graphqls
2022-12-15T13:06:51.075503Z INFO Apollo Router v1.6.0 // (c) Apollo Graph, Inc. // Licensed as ELv2 (https://go.apollo.dev/elv2)
2022-12-15T13:06:51.075552Z INFO Anonymous usage data is gathered to inform Apollo product development. See https://go.apollo.dev/o/privacy for more info.
2022-12-15T13:06:51.416303Z INFO healthcheck endpoint exposed at http://127.0.0.1:8088/health
2022-12-15T13:06:51.416603Z INFO GraphQL endpoint exposed at http://127.0.0.1:4000/ 🚀
$ curl --request POST --header 'content-type: application/json' --url http://localhost:4000/ --data '{"query":"query HandlesErrorsThrownInDeferredFragmentsQuery { computer(id: \"Computer1\") { id ...ComputerErrorField @defer } } fragment ComputerErrorField on Computer { errorField }"}' --header 'Accept: multipart/mixed; deferSpec=20220824'
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"errors":[{"message":"Subgraph errors redacted"}],"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":{"errorField":null},"path":["computer"]}]}
--graphql--
With:
$ ~/Tmp/router/router --dev --supergraph tests/defer/router/simple-supergraph.graphqls
2022-12-15T13:04:19.814946Z INFO Running with *development* mode settings which facilitate development experience (e.g., introspection enabled)
2022-12-15T13:04:19.815016Z INFO Apollo Router v1.6.0 // (c) Apollo Graph, Inc. // Licensed as ELv2 (https://go.apollo.dev/elv2)
2022-12-15T13:04:19.815022Z INFO Anonymous usage data is gathered to inform Apollo product development. See https://go.apollo.dev/o/privacy for more info.
2022-12-15T13:04:20.156597Z INFO healthcheck endpoint exposed at http://127.0.0.1:8088/health
2022-12-15T13:04:20.156905Z INFO GraphQL endpoint exposed at http://127.0.0.1:4000/ 🚀
$ curl --request POST --header 'content-type: application/json' --url http://localhost:4000/ --data '{"query":"query HandlesErrorsThrownInDeferredFragmentsQuery { computer(id: \"Computer1\") { id ...ComputerErrorField @defer } } fragment ComputerErrorField on Computer { errorField }"}' --header 'Accept: multipart/mixed; deferSpec=20220824'
--graphql
content-type: application/json
{"data":{"computer":{"id":"Computer1"}},"hasNext":true}
--graphql
content-type: application/json
{"hasNext":false,"incremental":[{"data":{"errorField":null},"path":["computer"],"errors":[{"message":"Error field","locations":[{"line":1,"column":93}],"path":["computer","errorField"],"extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"stacktrace":["Error: Error field"," at Object.errorField (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/computers.js:28:19)"," at field.resolve (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:56:26)"," at executeField (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:481:20)"," at executeFields (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)"," at completeObjectValue (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:914:10)"," at completeValue (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:635:12)"," at executeField (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:489:19)"," at executeFields (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:413:20)"," at executeOperation (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:344:14)"," at execute (/Users/bod/gitrepo/apollo-kotlin-0/tests/defer/router/subgraphs/computers/node_modules/graphql/execution/execute.js:136:20)"]}}}]}]}
--graphql--
I am testing without --dev though :thinking:
Could you add the include_subgraph_errors option so we can see the error messages?
my guess here is that without include_subgraph_errors, we replace the errors with an object that does not have a path, so we cannot affect it to the deferred response
I confirm. with include_subgraph_errors, I get the correct behavior too (same as with --dev).
alright, that should be an easy fix