From 49e937ef47e9f12cf9de2069c787b9025f62e073 Mon Sep 17 00:00:00 2001 From: RuntimeRacer Date: Thu, 1 Jul 2021 03:10:09 +0200 Subject: [PATCH] Added additional handling around this. GraphQL Handler will be able to handle invalid server responses and convert it into the expected format. However, this approach is not analyzing and properly checking the data received, it will just try and error like switch types. --- graphql.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/graphql.go b/graphql.go index 5c531472..35eee380 100644 --- a/graphql.go +++ b/graphql.go @@ -5,12 +5,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/runtimeracer/go-graphql-client/internal/jsonutil" + "io" "io/ioutil" "net/http" "strings" "golang.org/x/net/context/ctxhttp" + + "github.com/runtimeracer/go-graphql-client/internal/jsonutil" ) // Client is a GraphQL client. @@ -163,12 +165,8 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab body, _ := ioutil.ReadAll(resp.Body) return fmt.Errorf("non-200 OK status code: %v body: %q", resp.Status, body) } - var out struct { - Data *json.RawMessage - Errors errors - //Extensions interface{} // Unused. - } - err = json.NewDecoder(resp.Body).Decode(&out) + var out graphQLStdOut + out, err = c.unmarshalGraphQLResult(resp.Body) if err != nil { // TODO: Consider including response body in returned error, if deemed helpful. return err @@ -186,12 +184,49 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab return nil } +func (c *Client) unmarshalGraphQLResult(responseBody io.Reader) (graphQLStdOut, error) { + // Try unmarshal into default format + var output graphQLStdOut + err := json.NewDecoder(responseBody).Decode(&output) + if err != nil { + // TODO: Consider including response body in returned error, if deemed helpful. + // TODO: Add Warning message somehow that default is not working + var extFormat graphQLExtOut + err := json.NewDecoder(responseBody).Decode(&extFormat) + if err != nil { + // Output too weird or query error + return output, err + } + + // Convert Ext to default to meet criteria + output = graphQLStdOut{ + Data: extFormat.Data, + Errors: extFormat.Errors.ConvertToStandard(), + Extensions: extFormat.Extensions, + } + } + return output, nil +} + +type graphQLStdOut struct { + Data *json.RawMessage + Errors errors + Extensions interface{} +} + +type graphQLExtOut struct { + Data *json.RawMessage + Errors errorsExt + Extensions interface{} +} + // errors represents the "errors" array in a response from a GraphQL server. // If returned via error interface, the slice is expected to contain at least 1 element. // // Specification: https://facebook.github.io/graphql/#sec-Errors. -type errors []struct { - Message []interface{} +type errors []errorStruct +type errorStruct struct { + Message string Locations []struct { Line int Column int @@ -200,13 +235,54 @@ type errors []struct { // Error implements error interface. func (e errors) Error() string { - var stringOutput = make([]string, 0) + if len(e) == 0 { + return "" + } + return e[0].Message +} + +// errorsExt represents the "errors" array in a response from a GraphQL server. +// If returned via error interface, the slice is expected to contain at least 1 element. +// The "Ext" variant of this struct is able to handle non-standard implementations of the error message +type errorsExt []errorsExtStruct +type errorsExtStruct struct { + Message []interface{} + Locations []struct { + Line int + Column int + } +} + +// Error implements error interface. +func (e errorsExt) Error() string { + if len(e) == 0 { + return "" + } + + var stringOutput = make([]string, len(e[0].Message)) for i := range e[0].Message { stringOutput[i] = fmt.Sprintf("%v", e[0].Message[i]) } return strings.Join(stringOutput, ";") } +// ConvertToStandard translates extended error structs into structs matching the GraphQL Standard +func (e errorsExt) ConvertToStandard() errors { + if len(e) == 0 { + return nil + } + + standardError := make(errors, len(e)) + for i := range e[0].Message { + standardError[i] = errorStruct{ + Message: fmt.Sprintf("%v", e[0].Message[i]), + Locations: e[0].Locations, + } + } + + return standardError +} + type operationType uint8 const (