router icon indicating copy to clipboard operation
router copied to clipboard

@defer: error related to a field in a deferred fragment appears in chunk for non-deferred fragment

Open BoD opened this issue 3 years ago • 3 comments

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'

BoD avatar Sep 16 '22 13:09 BoD

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--

BoD avatar Sep 16 '22 15:09 BoD

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

Geal avatar Sep 19 '22 11:09 Geal

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)"
          ]
        }
      }
    }
  ]
}

BoD avatar Sep 19 '22 12:09 BoD

👋 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--

BoD avatar Nov 02 '22 17:11 BoD

I can still reproduce on 1.6.0 with the same steps as above

BoD avatar Dec 15 '22 11:12 BoD

thank you for testing it, I'll check

Geal avatar Dec 15 '22 12:12 Geal

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?

Geal avatar Dec 15 '22 12:12 Geal

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--

BoD avatar Dec 15 '22 13:12 BoD

I am testing without --dev though :thinking: Could you add the include_subgraph_errors option so we can see the error messages?

Geal avatar Dec 15 '22 13:12 Geal

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

Geal avatar Dec 15 '22 13:12 Geal

I confirm. with include_subgraph_errors, I get the correct behavior too (same as with --dev).

BoD avatar Dec 15 '22 13:12 BoD

alright, that should be an easy fix

Geal avatar Dec 15 '22 13:12 Geal