diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0cc9b4d --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/machinebox/graphql + +go 1.12 + +require ( + github.com/matryer/is v1.2.0 + github.com/pkg/errors v0.8.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..de4a465 --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/graphql.go b/graphql.go index 05c29b7..356932f 100644 --- a/graphql.go +++ b/graphql.go @@ -249,17 +249,27 @@ func ImmediatelyCloseReqBody() ClientOption { // modify the behaviour of the Client. type ClientOption func(*Client) -type graphErr struct { - Message string +// Location represents the location of a error +type Location struct { + Line int `json:"line,omitempty"` + Column int `json:"column,omitempty"` } -func (e graphErr) Error() string { - return "graphql: " + e.Message +// GraphError represents the standard graphql error described in https://graphql.github.io/graphql-spec/June2018/#sec-Errors +type GraphError struct { + Message string `json:"message"` + Path []interface{} `json:"path,omitempty"` + Locations []Location `json:"locations,omitempty"` + Extensions map[string]interface{} `json:"extensions,omitempty"` +} + +func (e GraphError) Error() string { + return fmt.Sprintf("graphql: %s", e.Message) } type graphResponse struct { Data interface{} - Errors []graphErr + Errors []GraphError } // Request is a GraphQL request. diff --git a/graphql_json_test.go b/graphql_json_test.go index a973d2d..d1a0438 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -159,3 +159,81 @@ func TestHeader(t *testing.T) { is.Equal(resp.Value, "some data") } + +func TestErrors(t *testing.T) { + is := is.New(t) + type errorSuit struct { + response string + statusCode int + expectedMessage string + expected GraphError + } + var suits = []errorSuit{ + errorSuit{ + response: `{ + "errors": [ + { + "message": "Name for character with ID 1002 could not be fetched.", + "locations": [ { "line": 6, "column": 7 } ], + "path": [ "hero", "heroFriends", 1, "name" ], + "extensions": { + "code": "CAN_NOT_FETCH_BY_ID", + "timestamp": "Fri Feb 9 14:33:09 UTC 2018" + } + } + ] + }`, + statusCode: 404, + expectedMessage: "graphql: Name for character with ID 1002 could not be fetched.", + expected: GraphError{ + Message: "Name for character with ID 1002 could not be fetched.", + Locations: []Location{ + Location{Line: 6, Column: 7}, + }, + Path: []interface{}{"hero", "heroFriends", float64(1), "name"}, + Extensions: map[string]interface{}{ + "code": "CAN_NOT_FETCH_BY_ID", + "timestamp": "Fri Feb 9 14:33:09 UTC 2018", + }, + }, + }, + errorSuit{ + response: `{ + "errors": [ + { + "message": "Server error" + } + ] + }`, + statusCode: 500, + expectedMessage: "graphql: Server error", + expected: GraphError{ + Message: "Server error", + }, + }, + } + for _, suit := range suits { + func() { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(suit.statusCode) + _, err := io.WriteString(w, suit.response) + is.NoErr(err) + })) + defer srv.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + client := NewClient(srv.URL) + req := NewRequest("query {}") + var resp struct { + Value string + } + err := client.Run(ctx, req, &resp) + if err != nil { + is.Equal(err.Error(), suit.expectedMessage) + is.Equal(err, suit.expected) + } else { + is.Fail() + } + }() + } +}