Skip to content

Commit

Permalink
Populate partial results on query with some failure.
Browse files Browse the repository at this point in the history
Sometimes, a part of the GraphQL query can fail, while the rest is
successful. The response will include correct data, and include errors
describing the part of the query that failed. (If the entire query
failed, the response data will simply be null.)

Unmarshal the GraphQL data into supplied structure before returning due
to non-zero GraphQL errors. This way, when there's a partial failure,
it's still possible to access the successful portion of the query.
  • Loading branch information
dmitshur committed Feb 23, 2018
1 parent d0549ed commit 8d3d310
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
7 changes: 5 additions & 2 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,14 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab
if err != nil {
return err
}
err = jsonutil.UnmarshalGraphQL(out.Data, v)
if err != nil {
return err
}
if len(out.Errors) > 0 {
return out.Errors
}
err = jsonutil.UnmarshalGraphQL(out.Data, v)
return err
return nil
}

// errors represents the "errors" array in a response from a GraphQL server.
Expand Down
83 changes: 83 additions & 0 deletions graphql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package graphql_test

import (
"context"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/shurcooL/graphql"
)

func TestClient_Query_partialResultWithErrorResponse(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
mustWrite(w, `{
"data": {
"node1": {
"id": "MDEyOklzc3VlQ29tbWVudDE2OTQwNzk0Ng=="
},
"node2": null
},
"errors": [
{
"message": "Could not resolve to a node with the global id of 'NotExist'",
"type": "NOT_FOUND",
"path": [
"node2"
],
"locations": [
{
"line": 10,
"column": 4
}
]
}
]
}`)
})
client := graphql.NewClient("/graphql", &http.Client{Transport: localRoundTripper{handler: mux}})

var q struct {
Node1 *struct {
ID graphql.ID
} `graphql:"node1: node(id: \"MDEyOklzc3VlQ29tbWVudDE2OTQwNzk0Ng==\")"`
Node2 *struct {
ID graphql.ID
} `graphql:"node2: node(id: \"NotExist\")"`
}
err := client.Query(context.Background(), &q, nil)
if err == nil {
t.Fatal("got error: nil, want: non-nil")
}
if got, want := err.Error(), "Could not resolve to a node with the global id of 'NotExist'"; got != want {
t.Errorf("got error: %v, want: %v", got, want)
}
if q.Node1 == nil || q.Node1.ID != "MDEyOklzc3VlQ29tbWVudDE2OTQwNzk0Ng==" {
t.Errorf("got wrong q.Node1: %v", q.Node1)
}
if q.Node2 != nil {
t.Errorf("got non-nil q.Node2: %v, want: nil", *q.Node2)
}
}

// localRoundTripper is an http.RoundTripper that executes HTTP transactions
// by using handler directly, instead of going over an HTTP connection.
type localRoundTripper struct {
handler http.Handler
}

func (l localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
w := httptest.NewRecorder()
l.handler.ServeHTTP(w, req)
return w.Result(), nil
}

func mustWrite(w io.Writer, s string) {
_, err := io.WriteString(w, s)
if err != nil {
panic(err)
}
}

0 comments on commit 8d3d310

Please sign in to comment.