diff --git a/error.go b/error.go index 162d4f9..1aa5ce3 100755 --- a/error.go +++ b/error.go @@ -1,42 +1,38 @@ -package graphql - -import "strings" - -// ErrorExtensions define fields that extend the standard graphql error shape -type ErrorExtensions struct { - Code string `json:"code"` -} - -// Error represents a graphql error -type Error struct { - Extensions ErrorExtensions `json:"extensions"` - Message string `json:"message"` -} - -func (e *Error) Error() string { - return e.Message -} - -// NewError returns a graphql error with the given code and message -func NewError(code string, message string) *Error { - return &Error{ - Message: message, - Extensions: ErrorExtensions{ - Code: code, - }, - } -} - -// ErrorList represents a list of errors -type ErrorList []error - -// Error returns a string representation of each error -func (list ErrorList) Error() string { - acc := []string{} - - for _, error := range list { - acc = append(acc, error.Error()) - } - - return strings.Join(acc, ". ") -} +package graphql + +import "strings" + +// Error represents a graphql error +type Error struct { + Extensions map[string]interface{} `json:"extensions"` + Message string `json:"message"` + Path []interface{} `json:"path,omitempty"` +} + +func (e *Error) Error() string { + return e.Message +} + +// NewError returns a graphql error with the given code and message +func NewError(code string, message string) *Error { + return &Error{ + Message: message, + Extensions: map[string]interface{}{ + "code": code, + }, + } +} + +// ErrorList represents a list of errors +type ErrorList []error + +// Error returns a string representation of each error +func (list ErrorList) Error() string { + acc := []string{} + + for _, error := range list { + acc = append(acc, error.Error()) + } + + return strings.Join(acc, ". ") +} diff --git a/error_test.go b/error_test.go index 75c6dca..bf56bcb 100755 --- a/error_test.go +++ b/error_test.go @@ -1,21 +1,21 @@ -package graphql - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSerializeError(t *testing.T) { - // marshal the 2 kinds of errors - errWithCode, _ := json.Marshal(NewError("ERROR_CODE", "foo")) - expected, _ := json.Marshal(map[string]interface{}{ - "extensions": map[string]interface{}{ - "code": "ERROR_CODE", - }, - "message": "foo", - }) - - assert.Equal(t, string(expected), string(errWithCode)) -} +package graphql + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSerializeError(t *testing.T) { + // marshal the 2 kinds of errors + errWithCode, _ := json.Marshal(NewError("ERROR_CODE", "foo")) + expected, _ := json.Marshal(map[string]interface{}{ + "extensions": map[string]interface{}{ + "code": "ERROR_CODE", + }, + "message": "foo", + }) + + assert.Equal(t, string(expected), string(errWithCode)) +} diff --git a/format.go b/format.go index 811f1d9..48bd7f5 100755 --- a/format.go +++ b/format.go @@ -1,66 +1,66 @@ -package graphql - -import ( - "fmt" - "strings" - - "github.com/vektah/gqlparser/v2/ast" -) - -func formatIndentPrefix(level int) string { - acc := "\n" - // build up the prefix - for i := 0; i <= level; i++ { - acc += " " - } - - return acc -} -func formatSelectionSelectionSet(level int, selectionSet ast.SelectionSet) string { - acc := " {" - // and any sub selection - acc += formatSelection(level+1, selectionSet) - acc += formatIndentPrefix(level) + "}" - - return acc -} - -func formatSelection(level int, selectionSet ast.SelectionSet) string { - acc := "" - - for _, selection := range selectionSet { - acc += formatIndentPrefix(level) - switch selection := selection.(type) { - case *ast.Field: - // add the field name - acc += selection.Name - if len(selection.SelectionSet) > 0 { - acc += formatSelectionSelectionSet(level, selection.SelectionSet) - } - case *ast.InlineFragment: - // print the fragment name - acc += fmt.Sprintf("... on %v", selection.TypeCondition) + - formatSelectionSelectionSet(level, selection.SelectionSet) - case *ast.FragmentSpread: - // print the fragment name - acc += "..." + selection.Name - } - } - - return acc -} - -// FormatSelectionSet returns a pretty printed version of a selection set -func FormatSelectionSet(selection ast.SelectionSet) string { - acc := "{" - - insides := formatSelection(0, selection) - - if strings.TrimSpace(insides) != "" { - acc += insides + "\n}" - } else { - acc += "}" - } - - return acc -} +package graphql + +import ( + "fmt" + "strings" + + "github.com/vektah/gqlparser/v2/ast" +) + +func formatIndentPrefix(level int) string { + acc := "\n" + // build up the prefix + for i := 0; i <= level; i++ { + acc += " " + } + + return acc +} +func formatSelectionSelectionSet(level int, selectionSet ast.SelectionSet) string { + acc := " {" + // and any sub selection + acc += formatSelection(level+1, selectionSet) + acc += formatIndentPrefix(level) + "}" + + return acc +} + +func formatSelection(level int, selectionSet ast.SelectionSet) string { + acc := "" + + for _, selection := range selectionSet { + acc += formatIndentPrefix(level) + switch selection := selection.(type) { + case *ast.Field: + // add the field name + acc += selection.Name + if len(selection.SelectionSet) > 0 { + acc += formatSelectionSelectionSet(level, selection.SelectionSet) + } + case *ast.InlineFragment: + // print the fragment name + acc += fmt.Sprintf("... on %v", selection.TypeCondition) + + formatSelectionSelectionSet(level, selection.SelectionSet) + case *ast.FragmentSpread: + // print the fragment name + acc += "..." + selection.Name + } + } + + return acc +} + +// FormatSelectionSet returns a pretty printed version of a selection set +func FormatSelectionSet(selection ast.SelectionSet) string { + acc := "{" + + insides := formatSelection(0, selection) + + if strings.TrimSpace(insides) != "" { + acc += insides + "\n}" + } else { + acc += "}" + } + + return acc +} diff --git a/format_test.go b/format_test.go index c9bfe73..ac9715b 100755 --- a/format_test.go +++ b/format_test.go @@ -1,57 +1,57 @@ -package graphql - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/vektah/gqlparser/v2/ast" -) - -func TestFormatSelectionSet(t *testing.T) { - // the table of sets to test - rows := []struct { - input ast.SelectionSet - expected string - }{ - { - ast.SelectionSet{}, - "{}", - }, - { - ast.SelectionSet{ - &ast.Field{Name: "firstName"}, - &ast.Field{Name: "friend", SelectionSet: ast.SelectionSet{&ast.Field{Name: "lastName"}}}, - }, - `{ - firstName - friend { - lastName - } -}`, - }, - { - ast.SelectionSet{&ast.FragmentSpread{Name: "MyFragment"}}, - `{ - ...MyFragment -}`, - }, - { - ast.SelectionSet{ - &ast.InlineFragment{ - TypeCondition: "MyType", - SelectionSet: ast.SelectionSet{&ast.Field{Name: "firstName"}}, - }, - }, - `{ - ... on MyType { - firstName - } -}`, - }, - } - - for _, row := range rows { - // make sure we get the expected result - assert.Equal(t, row.expected, FormatSelectionSet(row.input)) - } -} +package graphql + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vektah/gqlparser/v2/ast" +) + +func TestFormatSelectionSet(t *testing.T) { + // the table of sets to test + rows := []struct { + input ast.SelectionSet + expected string + }{ + { + ast.SelectionSet{}, + "{}", + }, + { + ast.SelectionSet{ + &ast.Field{Name: "firstName"}, + &ast.Field{Name: "friend", SelectionSet: ast.SelectionSet{&ast.Field{Name: "lastName"}}}, + }, + `{ + firstName + friend { + lastName + } +}`, + }, + { + ast.SelectionSet{&ast.FragmentSpread{Name: "MyFragment"}}, + `{ + ...MyFragment +}`, + }, + { + ast.SelectionSet{ + &ast.InlineFragment{ + TypeCondition: "MyType", + SelectionSet: ast.SelectionSet{&ast.Field{Name: "firstName"}}, + }, + }, + `{ + ... on MyType { + firstName + } +}`, + }, + } + + for _, row := range rows { + // make sure we get the expected result + assert.Equal(t, row.expected, FormatSelectionSet(row.input)) + } +} diff --git a/queryer.go b/queryer.go index 8783d00..ab481df 100755 --- a/queryer.go +++ b/queryer.go @@ -1,186 +1,200 @@ -package graphql - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "reflect" - - "github.com/vektah/gqlparser/v2/ast" -) - -// RemoteSchema encapsulates a particular schema that can be executed by sending network requests to the -// specified URL. -type RemoteSchema struct { - Schema *ast.Schema - URL string -} - -// QueryInput provides all of the information required to fire a query -type QueryInput struct { - Query string `json:"query"` - QueryDocument *ast.QueryDocument `json:"-"` - OperationName string `json:"operationName"` - Variables map[string]interface{} `json:"variables"` -} - -// String returns a guarenteed unique string that can be used to identify the input -func (i *QueryInput) String() string { - // let's just marshal the input - marshaled, err := json.Marshal(i) - if err != nil { - return "" - } - - // return the result - return string(marshaled) -} - -// Raw returns the "raw underlying value of the key" when used by dataloader -func (i *QueryInput) Raw() interface{} { - return i -} - -// Queryer is a interface for objects that can perform -type Queryer interface { - Query(context.Context, *QueryInput, interface{}) error -} - -// NetworkMiddleware are functions can be passed to SingleRequestQueryer.WithMiddleware to affect its internal -// behavior -type NetworkMiddleware func(*http.Request) error - -// QueryerWithMiddlewares is an interface for queryers that support network middlewares -type QueryerWithMiddlewares interface { - WithMiddlewares(wares []NetworkMiddleware) Queryer -} - -// HTTPQueryer is an interface for queryers that let you configure an underlying http.Client -type HTTPQueryer interface { - WithHTTPClient(client *http.Client) Queryer -} - -// HTTPQueryerWithMiddlewares is an interface for queryers that let you configure an underlying http.Client -// and accept middlewares -type HTTPQueryerWithMiddlewares interface { - WithHTTPClient(client *http.Client) Queryer - WithMiddlewares(wares []NetworkMiddleware) Queryer -} - -// Provided Implementations - -// MockSuccessQueryer responds with pre-defined value when executing a query -type MockSuccessQueryer struct { - Value interface{} -} - -// Query looks up the name of the query in the map of responses and returns the value -func (q *MockSuccessQueryer) Query(ctx context.Context, input *QueryInput, receiver interface{}) error { - // assume the mock is writing the same kind as the receiver - reflect.ValueOf(receiver).Elem().Set(reflect.ValueOf(q.Value)) - - // this will panic if something goes wrong - return nil -} - -// QueryerFunc responds to the query by calling the provided function -type QueryerFunc func(*QueryInput) (interface{}, error) - -// Query invokes the provided function and writes the response to the receiver -func (q QueryerFunc) Query(ctx context.Context, input *QueryInput, receiver interface{}) error { - // invoke the handler - response, err := q(input) - if err != nil { - return err - } - - // assume the mock is writing the same kind as the receiver - reflect.ValueOf(receiver).Elem().Set(reflect.ValueOf(response)) - - // no errors - return nil -} - -type NetworkQueryer struct { - URL string - Middlewares []NetworkMiddleware - Client *http.Client -} - -// SendQuery is responsible for sending the provided payload to the desingated URL -func (q *NetworkQueryer) SendQuery(ctx context.Context, payload []byte) ([]byte, error) { - // construct the initial request we will send to the client - req, err := http.NewRequest("POST", q.URL, bytes.NewBuffer(payload)) - if err != nil { - return nil, err - } - // add the current context to the request - acc := req.WithContext(ctx) - acc.Header.Set("Content-Type", "application/json") - - // we could have any number of middlewares that we have to go through so - for _, mware := range q.Middlewares { - err := mware(acc) - if err != nil { - return nil, err - } - } - - // fire the response to the queryer's url - if q.Client == nil { - q.Client = &http.Client{} - } - - resp, err := q.Client.Do(acc) - if err != nil { - return nil, err - } - - // read the full body - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - // we're done - return body, err -} - -// ExtractErrors takes the result from a remote query and writes it to the provided pointer -func (q *NetworkQueryer) ExtractErrors(result map[string]interface{}) error { - // if there is an error - if _, ok := result["errors"]; ok { - // a list of errors from the response - errList := ErrorList{} - - // build up a list of errors - errs, ok := result["errors"].([]interface{}) - if !ok { - return errors.New("errors was not a list") - } - - // a list of error messages - for _, err := range errs { - obj, ok := err.(map[string]interface{}) - if !ok { - return errors.New("encountered non-object error") - } - - message, ok := obj["message"].(string) - if !ok { - return errors.New("error message was not a string") - } - - errList = append(errList, NewError("", message)) - } - - return errList - } - - // pass the result along - return nil -} +package graphql + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "reflect" + + "github.com/vektah/gqlparser/v2/ast" +) + +// RemoteSchema encapsulates a particular schema that can be executed by sending network requests to the +// specified URL. +type RemoteSchema struct { + Schema *ast.Schema + URL string +} + +// QueryInput provides all of the information required to fire a query +type QueryInput struct { + Query string `json:"query"` + QueryDocument *ast.QueryDocument `json:"-"` + OperationName string `json:"operationName"` + Variables map[string]interface{} `json:"variables"` +} + +// String returns a guarenteed unique string that can be used to identify the input +func (i *QueryInput) String() string { + // let's just marshal the input + marshaled, err := json.Marshal(i) + if err != nil { + return "" + } + + // return the result + return string(marshaled) +} + +// Raw returns the "raw underlying value of the key" when used by dataloader +func (i *QueryInput) Raw() interface{} { + return i +} + +// Queryer is a interface for objects that can perform +type Queryer interface { + Query(context.Context, *QueryInput, interface{}) error +} + +// NetworkMiddleware are functions can be passed to SingleRequestQueryer.WithMiddleware to affect its internal +// behavior +type NetworkMiddleware func(*http.Request) error + +// QueryerWithMiddlewares is an interface for queryers that support network middlewares +type QueryerWithMiddlewares interface { + WithMiddlewares(wares []NetworkMiddleware) Queryer +} + +// HTTPQueryer is an interface for queryers that let you configure an underlying http.Client +type HTTPQueryer interface { + WithHTTPClient(client *http.Client) Queryer +} + +// HTTPQueryerWithMiddlewares is an interface for queryers that let you configure an underlying http.Client +// and accept middlewares +type HTTPQueryerWithMiddlewares interface { + WithHTTPClient(client *http.Client) Queryer + WithMiddlewares(wares []NetworkMiddleware) Queryer +} + +// Provided Implementations + +// MockSuccessQueryer responds with pre-defined value when executing a query +type MockSuccessQueryer struct { + Value interface{} +} + +// Query looks up the name of the query in the map of responses and returns the value +func (q *MockSuccessQueryer) Query(ctx context.Context, input *QueryInput, receiver interface{}) error { + // assume the mock is writing the same kind as the receiver + reflect.ValueOf(receiver).Elem().Set(reflect.ValueOf(q.Value)) + + // this will panic if something goes wrong + return nil +} + +// QueryerFunc responds to the query by calling the provided function +type QueryerFunc func(*QueryInput) (interface{}, error) + +// Query invokes the provided function and writes the response to the receiver +func (q QueryerFunc) Query(ctx context.Context, input *QueryInput, receiver interface{}) error { + // invoke the handler + response, err := q(input) + if err != nil { + return err + } + + // assume the mock is writing the same kind as the receiver + reflect.ValueOf(receiver).Elem().Set(reflect.ValueOf(response)) + + // no errors + return nil +} + +type NetworkQueryer struct { + URL string + Middlewares []NetworkMiddleware + Client *http.Client +} + +// SendQuery is responsible for sending the provided payload to the desingated URL +func (q *NetworkQueryer) SendQuery(ctx context.Context, payload []byte) ([]byte, error) { + // construct the initial request we will send to the client + req, err := http.NewRequest("POST", q.URL, bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + // add the current context to the request + acc := req.WithContext(ctx) + acc.Header.Set("Content-Type", "application/json") + + // we could have any number of middlewares that we have to go through so + for _, mware := range q.Middlewares { + err := mware(acc) + if err != nil { + return nil, err + } + } + + // fire the response to the queryer's url + if q.Client == nil { + q.Client = &http.Client{} + } + + resp, err := q.Client.Do(acc) + if err != nil { + return nil, err + } + + // read the full body + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // we're done + return body, err +} + +// ExtractErrors takes the result from a remote query and writes it to the provided pointer +func (q *NetworkQueryer) ExtractErrors(result map[string]interface{}) error { + // if there is an error + if _, ok := result["errors"]; ok { + // a list of errors from the response + errList := ErrorList{} + + // build up a list of errors + errs, ok := result["errors"].([]interface{}) + if !ok { + return errors.New("errors was not a list") + } + + // a list of error messages + for _, err := range errs { + obj, ok := err.(map[string]interface{}) + if !ok { + return errors.New("encountered non-object error") + } + + message, ok := obj["message"].(string) + if !ok { + return errors.New("error message was not a string") + } + + var extensions map[string]interface{} + if e, ok := obj["extensions"].(map[string]interface{}); ok { + extensions = e + } + + var path []interface{} + if p, ok := obj["path"].([]interface{}); ok { + path = p + } + + errList = append(errList, &Error{ + Message: message, + Path: path, + Extensions: extensions, + }) + } + + return errList + } + + // pass the result along + return nil +} diff --git a/queryerMultiOp.go b/queryerMultiOp.go index efebb71..f2d4d9b 100755 --- a/queryerMultiOp.go +++ b/queryerMultiOp.go @@ -1,136 +1,136 @@ -package graphql - -import ( - "context" - "encoding/json" - "errors" - "net/http" - "time" - - "github.com/graph-gophers/dataloader" - "github.com/mitchellh/mapstructure" -) - -// MultiOpQueryer is a queryer that will batch subsequent query on some interval into a single network request -// to a single target -type MultiOpQueryer struct { - MaxBatchSize int - BatchInterval time.Duration - - // internals for bundling queries - queryer *NetworkQueryer - loader *dataloader.Loader -} - -// NewMultiOpQueryer returns a MultiOpQueryer with the provided paramters -func NewMultiOpQueryer(url string, interval time.Duration, maxBatchSize int) *MultiOpQueryer { - queryer := &MultiOpQueryer{ - MaxBatchSize: maxBatchSize, - BatchInterval: interval, - } - - // instantiate a dataloader we can use for queries - queryer.loader = dataloader.NewBatchedLoader( - queryer.loadQuery, - dataloader.WithCache(&dataloader.NoCache{}), - dataloader.WithWait(interval), - dataloader.WithBatchCapacity(maxBatchSize), - ) - - // instantiate a network queryer we can use later - queryer.queryer = &NetworkQueryer{ - URL: url, - } - - // we're done creating the queryer - return queryer -} - -// WithMiddlewares lets the user assign middlewares to the queryer -func (q *MultiOpQueryer) WithMiddlewares(mwares []NetworkMiddleware) Queryer { - q.queryer.Middlewares = mwares - return q -} - -// WithHTTPClient lets the user configure the client to use when making network requests -func (q *MultiOpQueryer) WithHTTPClient(client *http.Client) Queryer { - q.queryer.Client = client - return q -} - -// Query bundles queries that happen within the given interval into a single network request -// whose body is a list of the operation payload. -func (q *MultiOpQueryer) Query(ctx context.Context, input *QueryInput, receiver interface{}) error { - // process the input - result, err := q.loader.Load(ctx, input)() - if err != nil { - return err - } - - unmarshaled, ok := result.(map[string]interface{}) - if !ok { - return errors.New("Result from dataloader was not an object") - } - - // format the result as needed - err = q.queryer.ExtractErrors(unmarshaled) - if err != nil { - return err - } - - // assign the result under the data key to the receiver - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - TagName: "json", - Result: receiver, - }) - if err != nil { - return err - } - - // the only way for things to go wrong now happen while decoding - return decoder.Decode(unmarshaled["data"]) -} - -func (q *MultiOpQueryer) loadQuery(ctx context.Context, keys dataloader.Keys) []*dataloader.Result { - // a place to store the results - results := []*dataloader.Result{} - - // the keys serialize to the correct representation - payload, err := json.Marshal(keys) - if err != nil { - // we need to result the same error for each result - for range keys { - results = append(results, &dataloader.Result{Error: err}) - } - return results - } - - // send the payload to the server - response, err := q.queryer.SendQuery(ctx, payload) - if err != nil { - // we need to result the same error for each result - for range keys { - results = append(results, &dataloader.Result{Error: err}) - } - return results - } - - // a place to handle each result - queryResults := []map[string]interface{}{} - err = json.Unmarshal(response, &queryResults) - if err != nil { - // we need to result the same error for each result - for range keys { - results = append(results, &dataloader.Result{Error: err}) - } - return results - } - - // take the result from the query and turn it into something dataloader is okay with - for _, result := range queryResults { - results = append(results, &dataloader.Result{Data: result}) - } - - // return the results - return results -} +package graphql + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "time" + + "github.com/graph-gophers/dataloader" + "github.com/mitchellh/mapstructure" +) + +// MultiOpQueryer is a queryer that will batch subsequent query on some interval into a single network request +// to a single target +type MultiOpQueryer struct { + MaxBatchSize int + BatchInterval time.Duration + + // internals for bundling queries + queryer *NetworkQueryer + loader *dataloader.Loader +} + +// NewMultiOpQueryer returns a MultiOpQueryer with the provided paramters +func NewMultiOpQueryer(url string, interval time.Duration, maxBatchSize int) *MultiOpQueryer { + queryer := &MultiOpQueryer{ + MaxBatchSize: maxBatchSize, + BatchInterval: interval, + } + + // instantiate a dataloader we can use for queries + queryer.loader = dataloader.NewBatchedLoader( + queryer.loadQuery, + dataloader.WithCache(&dataloader.NoCache{}), + dataloader.WithWait(interval), + dataloader.WithBatchCapacity(maxBatchSize), + ) + + // instantiate a network queryer we can use later + queryer.queryer = &NetworkQueryer{ + URL: url, + } + + // we're done creating the queryer + return queryer +} + +// WithMiddlewares lets the user assign middlewares to the queryer +func (q *MultiOpQueryer) WithMiddlewares(mwares []NetworkMiddleware) Queryer { + q.queryer.Middlewares = mwares + return q +} + +// WithHTTPClient lets the user configure the client to use when making network requests +func (q *MultiOpQueryer) WithHTTPClient(client *http.Client) Queryer { + q.queryer.Client = client + return q +} + +// Query bundles queries that happen within the given interval into a single network request +// whose body is a list of the operation payload. +func (q *MultiOpQueryer) Query(ctx context.Context, input *QueryInput, receiver interface{}) error { + // process the input + result, err := q.loader.Load(ctx, input)() + if err != nil { + return err + } + + unmarshaled, ok := result.(map[string]interface{}) + if !ok { + return errors.New("Result from dataloader was not an object") + } + + // format the result as needed + err = q.queryer.ExtractErrors(unmarshaled) + if err != nil { + return err + } + + // assign the result under the data key to the receiver + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + TagName: "json", + Result: receiver, + }) + if err != nil { + return err + } + + // the only way for things to go wrong now happen while decoding + return decoder.Decode(unmarshaled["data"]) +} + +func (q *MultiOpQueryer) loadQuery(ctx context.Context, keys dataloader.Keys) []*dataloader.Result { + // a place to store the results + results := []*dataloader.Result{} + + // the keys serialize to the correct representation + payload, err := json.Marshal(keys) + if err != nil { + // we need to result the same error for each result + for range keys { + results = append(results, &dataloader.Result{Error: err}) + } + return results + } + + // send the payload to the server + response, err := q.queryer.SendQuery(ctx, payload) + if err != nil { + // we need to result the same error for each result + for range keys { + results = append(results, &dataloader.Result{Error: err}) + } + return results + } + + // a place to handle each result + queryResults := []map[string]interface{}{} + err = json.Unmarshal(response, &queryResults) + if err != nil { + // we need to result the same error for each result + for range keys { + results = append(results, &dataloader.Result{Error: err}) + } + return results + } + + // take the result from the query and turn it into something dataloader is okay with + for _, result := range queryResults { + results = append(results, &dataloader.Result{Data: result}) + } + + // return the results + return results +} diff --git a/queryerMultiOp_test.go b/queryerMultiOp_test.go index afe3747..188b169 100755 --- a/queryerMultiOp_test.go +++ b/queryerMultiOp_test.go @@ -1,103 +1,103 @@ -package graphql - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "net/http" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -type contextKey int - -const ( - requestLabel contextKey = iota - responseCount -) - -func TestNewMultiOpQueryer(t *testing.T) { - queryer := NewMultiOpQueryer("foo", 1*time.Millisecond, 100) - - // make sure the queryer config is all correct - assert.Equal(t, "foo", queryer.queryer.URL) - assert.Equal(t, 1*time.Millisecond, queryer.BatchInterval) - assert.Equal(t, 100, queryer.MaxBatchSize) -} - -func TestMultiOpQueryer_batchesRequests(t *testing.T) { - nCalled := 0 - - // the bundle time of the queryer - interval := 10 * time.Millisecond - - // create a queryer that we will use that has a client that keeps track of the - // number of times it was called - queryer := NewMultiOpQueryer("foo", interval, 100).WithHTTPClient(&http.Client{ - Transport: roundTripFunc(func(req *http.Request) *http.Response { - nCalled++ - - label := req.Context().Value(requestLabel).(string) - - body := "" - for i := 0; i < req.Context().Value(responseCount).(int); i++ { - body += fmt.Sprintf(`{ "data": { "nCalled": "%s:%v" } },`, label, nCalled) - } - - return &http.Response{ - StatusCode: 200, - // Send response to be tested - Body: ioutil.NopCloser(bytes.NewBufferString(fmt.Sprintf(`[ - %s - ]`, body[:len(body)-1]))), - // Must be set to non-nil value or it panics - Header: make(http.Header), - } - }), - }) - - // the query we will be batching - query := "{ nCalled }" - - // places to hold the results - result1 := map[string]interface{}{} - result2 := map[string]interface{}{} - result3 := map[string]interface{}{} - - // query once on its own - ctx1 := context.WithValue(context.WithValue(context.Background(), requestLabel, "1"), responseCount, 1) - queryer.Query(ctx1, &QueryInput{Query: query}, &result1) - - // wait a bit - time.Sleep(interval + 10*time.Millisecond) - - // query twice back to back - count := &sync.WaitGroup{} - count.Add(1) - go func() { - ctx2 := context.WithValue(context.WithValue(context.Background(), requestLabel, "2"), responseCount, 2) - queryer.Query(ctx2, &QueryInput{Query: query}, &result2) - count.Done() - }() - count.Add(1) - go func() { - ctx3 := context.WithValue(context.WithValue(context.Background(), requestLabel, "2"), responseCount, 2) - queryer.Query(ctx3, &QueryInput{Query: query}, &result3) - count.Done() - }() - - // wait for the queries to be done - count.Wait() - - // make sure that we only invoked the client twice - assert.Equal(t, 2, nCalled) - - // make sure that we got the right results - assert.Equal(t, map[string]interface{}{"nCalled": "1:1"}, result1) - assert.Equal(t, map[string]interface{}{"nCalled": "2:2"}, result2) - assert.Equal(t, map[string]interface{}{"nCalled": "2:2"}, result3) -} +package graphql + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "net/http" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type contextKey int + +const ( + requestLabel contextKey = iota + responseCount +) + +func TestNewMultiOpQueryer(t *testing.T) { + queryer := NewMultiOpQueryer("foo", 1*time.Millisecond, 100) + + // make sure the queryer config is all correct + assert.Equal(t, "foo", queryer.queryer.URL) + assert.Equal(t, 1*time.Millisecond, queryer.BatchInterval) + assert.Equal(t, 100, queryer.MaxBatchSize) +} + +func TestMultiOpQueryer_batchesRequests(t *testing.T) { + nCalled := 0 + + // the bundle time of the queryer + interval := 10 * time.Millisecond + + // create a queryer that we will use that has a client that keeps track of the + // number of times it was called + queryer := NewMultiOpQueryer("foo", interval, 100).WithHTTPClient(&http.Client{ + Transport: roundTripFunc(func(req *http.Request) *http.Response { + nCalled++ + + label := req.Context().Value(requestLabel).(string) + + body := "" + for i := 0; i < req.Context().Value(responseCount).(int); i++ { + body += fmt.Sprintf(`{ "data": { "nCalled": "%s:%v" } },`, label, nCalled) + } + + return &http.Response{ + StatusCode: 200, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBufferString(fmt.Sprintf(`[ + %s + ]`, body[:len(body)-1]))), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + }), + }) + + // the query we will be batching + query := "{ nCalled }" + + // places to hold the results + result1 := map[string]interface{}{} + result2 := map[string]interface{}{} + result3 := map[string]interface{}{} + + // query once on its own + ctx1 := context.WithValue(context.WithValue(context.Background(), requestLabel, "1"), responseCount, 1) + queryer.Query(ctx1, &QueryInput{Query: query}, &result1) + + // wait a bit + time.Sleep(interval + 10*time.Millisecond) + + // query twice back to back + count := &sync.WaitGroup{} + count.Add(1) + go func() { + ctx2 := context.WithValue(context.WithValue(context.Background(), requestLabel, "2"), responseCount, 2) + queryer.Query(ctx2, &QueryInput{Query: query}, &result2) + count.Done() + }() + count.Add(1) + go func() { + ctx3 := context.WithValue(context.WithValue(context.Background(), requestLabel, "2"), responseCount, 2) + queryer.Query(ctx3, &QueryInput{Query: query}, &result3) + count.Done() + }() + + // wait for the queries to be done + count.Wait() + + // make sure that we only invoked the client twice + assert.Equal(t, 2, nCalled) + + // make sure that we got the right results + assert.Equal(t, map[string]interface{}{"nCalled": "1:1"}, result1) + assert.Equal(t, map[string]interface{}{"nCalled": "2:2"}, result2) + assert.Equal(t, map[string]interface{}{"nCalled": "2:2"}, result3) +} diff --git a/queryer_test.go b/queryer_test.go index 8bbac35..0dd363a 100755 --- a/queryer_test.go +++ b/queryer_test.go @@ -1,436 +1,436 @@ -package graphql - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -type roundTripFunc func(req *http.Request) *http.Response - -// RoundTrip . -func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -func TestQueryerFunc_success(t *testing.T) { - expected := map[string]interface{}{"hello": "world"} - - queryer := QueryerFunc( - func(*QueryInput) (interface{}, error) { - return expected, nil - }, - ) - - // a place to write the result - result := map[string]interface{}{} - - err := queryer.Query(context.Background(), &QueryInput{}, &result) - if err != nil { - t.Error(err.Error()) - return - } - - // make sure we copied the right result - assert.Equal(t, expected, result) -} - -func TestQueryerFunc_failure(t *testing.T) { - expected := errors.New("message") - - queryer := QueryerFunc( - func(*QueryInput) (interface{}, error) { - return nil, expected - }, - ) - - err := queryer.Query(context.Background(), &QueryInput{}, &map[string]interface{}{}) - - // make sure we got the right error - assert.Equal(t, expected, err) -} - -func TestHTTPQueryerBasicCases(t *testing.T) { - // this test run a suite of tests for every queryer in the table - queryerTable := []struct { - name string - queryer HTTPQueryer - wrapInList bool - }{ - { - "Single Request", - NewSingleRequestQueryer("hello"), - false, - }, - { - "MultiOp", - NewMultiOpQueryer("hello", 1*time.Millisecond, 10), - true, - }, - } - - // for each queryer we have to test - for _, row := range queryerTable { - t.Run(row.name, func(t *testing.T) { - t.Run("Sends Queries", func(t *testing.T) { - // build a query to test should be equivalent to - // targetQueryBody := ` - // { - // hello(world: "hello") { - // world - // } - // } - // ` - - // the result we expect back - expected := map[string]interface{}{ - "foo": "bar", - } - - // the corresponding query document - query := ` - { - hello(world: "hello") { - world - } - } - ` - - httpQueryer := row.queryer.WithHTTPClient(&http.Client{ - Transport: roundTripFunc(func(req *http.Request) *http.Response { - var result interface{} - if row.wrapInList { - result = []map[string]interface{}{{"data": expected}} - } else { - result = map[string]interface{}{"data": expected} - } - - // serialize the json we want to send back - marshaled, err := json.Marshal(result) - - // if something went wrong - if err != nil { - return &http.Response{ - StatusCode: 500, - Body: ioutil.NopCloser(bytes.NewBufferString("Something went wrong")), - Header: make(http.Header), - } - } - - return &http.Response{ - StatusCode: 200, - // Send response to be tested - Body: ioutil.NopCloser(bytes.NewBuffer(marshaled)), - // Must be set to non-nil value or it panics - Header: make(http.Header), - } - }), - }) - - // get the response of the query - result := map[string]interface{}{} - err := httpQueryer.Query(context.Background(), &QueryInput{Query: query}, &result) - if err != nil { - t.Error(err) - return - } - if result == nil { - t.Error("Did not get a result back") - return - } - - // make sure we got what we expected - assert.Equal(t, expected, result) - }) - - t.Run("Handles error response", func(t *testing.T) { - // the table for the tests - for _, errorRow := range []struct { - Message string - ErrorShape interface{} - }{ - { - "Well Structured Error", - []map[string]interface{}{ - { - "message": "message", - }, - }, - }, - { - "Errors Not Lists", - map[string]interface{}{ - "message": "message", - }, - }, - { - "Errors Lists of Not Strings", - []string{"hello"}, - }, - { - "Errors No messages", - []map[string]interface{}{}, - }, - { - "Message not string", - []map[string]interface{}{ - { - "message": true, - }, - }, - }, - { - "No Errors", - nil, - }, - } { - t.Run(errorRow.Message, func(t *testing.T) { - // the corresponding query document - query := ` - { - hello(world: "hello") { - world - } - } - ` - - queryer := row.queryer.WithHTTPClient(&http.Client{ - Transport: roundTripFunc(func(req *http.Request) *http.Response { - response := map[string]interface{}{ - "data": nil, - } - - // if we are supposed to have an error - if errorRow.ErrorShape != nil { - response["errors"] = errorRow.ErrorShape - } - - var finalResponse interface{} = response - if row.wrapInList { - finalResponse = []map[string]interface{}{response} - } - - // serialize the json we want to send back - result, err := json.Marshal(finalResponse) - // if something went wrong - if err != nil { - return &http.Response{ - StatusCode: 500, - Body: ioutil.NopCloser(bytes.NewBufferString("Something went wrong")), - Header: make(http.Header), - } - } - - return &http.Response{ - StatusCode: 200, - // Send response to be tested - Body: ioutil.NopCloser(bytes.NewBuffer(result)), - // Must be set to non-nil value or it panics - Header: make(http.Header), - } - }), - }) - - // get the response of the query - result := map[string]interface{}{} - err := queryer.Query(context.Background(), &QueryInput{Query: query}, &result) - - // if we're supposed to hav ean error - if errorRow.ErrorShape != nil { - assert.NotNil(t, err) - } else { - assert.Nil(t, err) - } - }) - - } - }) - - t.Run("Error Lists", func(t *testing.T) { - // the corresponding query document - query := ` - { - hello(world: "hello") { - world - } - } - ` - - queryer := row.queryer.WithHTTPClient(&http.Client{ - Transport: roundTripFunc(func(req *http.Request) *http.Response { - response := `{ - "data": null, - "errors": [ - {"message":"hello"} - ] - }` - if row.wrapInList { - response = fmt.Sprintf("[%s]", response) - } - - return &http.Response{ - StatusCode: 200, - // Send response to be tested - Body: ioutil.NopCloser(bytes.NewBuffer([]byte(response))), - // Must be set to non-nil value or it panics - Header: make(http.Header), - } - }), - }) - - // get the error of the query - err := queryer.Query(context.Background(), &QueryInput{Query: query}, &map[string]interface{}{}) - // if we didn't get an error at all - if err == nil { - t.Error("Did not encounter an error") - return - } - - _, ok := err.(ErrorList) - if !ok { - t.Errorf("response of queryer was not an error list: %v", err.Error()) - return - } - }) - - t.Run("Responds with Error", func(t *testing.T) { - // the corresponding query document - query := ` - { - hello - } - ` - - queryer := row.queryer.WithHTTPClient(&http.Client{ - Transport: roundTripFunc(func(req *http.Request) *http.Response { - // send an error back - return &http.Response{ - StatusCode: 500, - Body: ioutil.NopCloser(bytes.NewBufferString("Something went wrong")), - Header: make(http.Header), - } - }), - }) - - // get the response of the query - var result interface{} - err := queryer.Query(context.Background(), &QueryInput{Query: query}, result) - if err == nil { - t.Error("Did not receive an error") - return - } - }) - }) - } -} - -func TestQueryerWithMiddlewares(t *testing.T) { - queryerTable := []struct { - name string - queryer HTTPQueryerWithMiddlewares - wrapInList bool - }{ - { - "Single Request", - NewSingleRequestQueryer("hello"), - false, - }, - { - "MultiOp", - NewMultiOpQueryer("hello", 1*time.Millisecond, 10), - true, - }, - } - - for _, row := range queryerTable { - t.Run(row.name, func(t *testing.T) { - t.Run("Middleware Failures", func(t *testing.T) { - queryer := row.queryer.WithMiddlewares([]NetworkMiddleware{ - func(r *http.Request) error { - return errors.New("This One") - }, - }) - - // the input to the query - input := &QueryInput{ - Query: "", - } - - // fire the query - err := queryer.Query(context.Background(), input, &map[string]interface{}{}) - if err == nil { - t.Error("Did not enounter an error when we should have") - return - } - if err.Error() != "This One" { - t.Errorf("Did not encountered expected error message: Expected 'This One', found %v", err.Error()) - } - }) - - t.Run("Middlware success", func(t *testing.T) { - queryer := row.queryer.WithMiddlewares([]NetworkMiddleware{ - func(r *http.Request) error { - r.Header.Set("Hello", "World") - - return nil - }, - }) - - if q, ok := queryer.(HTTPQueryerWithMiddlewares); ok { - queryer = q.WithHTTPClient(&http.Client{ - Transport: roundTripFunc(func(req *http.Request) *http.Response { - // if we did not get the right header value - if req.Header.Get("Hello") != "World" { - return &http.Response{ - StatusCode: http.StatusExpectationFailed, - // Send response to be tested - Body: ioutil.NopCloser(bytes.NewBufferString("Did not recieve the right header")), - // Must be set to non-nil value or it panics - Header: make(http.Header), - } - } - - // serialize the json we want to send back - result, _ := json.Marshal(map[string]interface{}{ - "allUsers": []string{ - "John Jacob", - "Jinglehymer Schmidt", - }, - }) - if row.wrapInList { - result = []byte(fmt.Sprintf("[%s]", string(result))) - } - - return &http.Response{ - StatusCode: 200, - // Send response to be tested - Body: ioutil.NopCloser(bytes.NewBuffer(result)), - // Must be set to non-nil value or it panics - Header: make(http.Header), - } - }), - }) - } - - // the input to the query - input := &QueryInput{ - Query: "", - } - - err := queryer.Query(context.Background(), input, &map[string]interface{}{}) - if err != nil { - t.Error(err.Error()) - return - } - }) - }) - } -} +package graphql + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type roundTripFunc func(req *http.Request) *http.Response + +// RoundTrip . +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +func TestQueryerFunc_success(t *testing.T) { + expected := map[string]interface{}{"hello": "world"} + + queryer := QueryerFunc( + func(*QueryInput) (interface{}, error) { + return expected, nil + }, + ) + + // a place to write the result + result := map[string]interface{}{} + + err := queryer.Query(context.Background(), &QueryInput{}, &result) + if err != nil { + t.Error(err.Error()) + return + } + + // make sure we copied the right result + assert.Equal(t, expected, result) +} + +func TestQueryerFunc_failure(t *testing.T) { + expected := errors.New("message") + + queryer := QueryerFunc( + func(*QueryInput) (interface{}, error) { + return nil, expected + }, + ) + + err := queryer.Query(context.Background(), &QueryInput{}, &map[string]interface{}{}) + + // make sure we got the right error + assert.Equal(t, expected, err) +} + +func TestHTTPQueryerBasicCases(t *testing.T) { + // this test run a suite of tests for every queryer in the table + queryerTable := []struct { + name string + queryer HTTPQueryer + wrapInList bool + }{ + { + "Single Request", + NewSingleRequestQueryer("hello"), + false, + }, + { + "MultiOp", + NewMultiOpQueryer("hello", 1*time.Millisecond, 10), + true, + }, + } + + // for each queryer we have to test + for _, row := range queryerTable { + t.Run(row.name, func(t *testing.T) { + t.Run("Sends Queries", func(t *testing.T) { + // build a query to test should be equivalent to + // targetQueryBody := ` + // { + // hello(world: "hello") { + // world + // } + // } + // ` + + // the result we expect back + expected := map[string]interface{}{ + "foo": "bar", + } + + // the corresponding query document + query := ` + { + hello(world: "hello") { + world + } + } + ` + + httpQueryer := row.queryer.WithHTTPClient(&http.Client{ + Transport: roundTripFunc(func(req *http.Request) *http.Response { + var result interface{} + if row.wrapInList { + result = []map[string]interface{}{{"data": expected}} + } else { + result = map[string]interface{}{"data": expected} + } + + // serialize the json we want to send back + marshaled, err := json.Marshal(result) + + // if something went wrong + if err != nil { + return &http.Response{ + StatusCode: 500, + Body: ioutil.NopCloser(bytes.NewBufferString("Something went wrong")), + Header: make(http.Header), + } + } + + return &http.Response{ + StatusCode: 200, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBuffer(marshaled)), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + }), + }) + + // get the response of the query + result := map[string]interface{}{} + err := httpQueryer.Query(context.Background(), &QueryInput{Query: query}, &result) + if err != nil { + t.Error(err) + return + } + if result == nil { + t.Error("Did not get a result back") + return + } + + // make sure we got what we expected + assert.Equal(t, expected, result) + }) + + t.Run("Handles error response", func(t *testing.T) { + // the table for the tests + for _, errorRow := range []struct { + Message string + ErrorShape interface{} + }{ + { + "Well Structured Error", + []map[string]interface{}{ + { + "message": "message", + }, + }, + }, + { + "Errors Not Lists", + map[string]interface{}{ + "message": "message", + }, + }, + { + "Errors Lists of Not Strings", + []string{"hello"}, + }, + { + "Errors No messages", + []map[string]interface{}{}, + }, + { + "Message not string", + []map[string]interface{}{ + { + "message": true, + }, + }, + }, + { + "No Errors", + nil, + }, + } { + t.Run(errorRow.Message, func(t *testing.T) { + // the corresponding query document + query := ` + { + hello(world: "hello") { + world + } + } + ` + + queryer := row.queryer.WithHTTPClient(&http.Client{ + Transport: roundTripFunc(func(req *http.Request) *http.Response { + response := map[string]interface{}{ + "data": nil, + } + + // if we are supposed to have an error + if errorRow.ErrorShape != nil { + response["errors"] = errorRow.ErrorShape + } + + var finalResponse interface{} = response + if row.wrapInList { + finalResponse = []map[string]interface{}{response} + } + + // serialize the json we want to send back + result, err := json.Marshal(finalResponse) + // if something went wrong + if err != nil { + return &http.Response{ + StatusCode: 500, + Body: ioutil.NopCloser(bytes.NewBufferString("Something went wrong")), + Header: make(http.Header), + } + } + + return &http.Response{ + StatusCode: 200, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBuffer(result)), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + }), + }) + + // get the response of the query + result := map[string]interface{}{} + err := queryer.Query(context.Background(), &QueryInput{Query: query}, &result) + + // if we're supposed to hav ean error + if errorRow.ErrorShape != nil { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + + } + }) + + t.Run("Error Lists", func(t *testing.T) { + // the corresponding query document + query := ` + { + hello(world: "hello") { + world + } + } + ` + + queryer := row.queryer.WithHTTPClient(&http.Client{ + Transport: roundTripFunc(func(req *http.Request) *http.Response { + response := `{ + "data": null, + "errors": [ + {"message":"hello"} + ] + }` + if row.wrapInList { + response = fmt.Sprintf("[%s]", response) + } + + return &http.Response{ + StatusCode: 200, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBuffer([]byte(response))), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + }), + }) + + // get the error of the query + err := queryer.Query(context.Background(), &QueryInput{Query: query}, &map[string]interface{}{}) + // if we didn't get an error at all + if err == nil { + t.Error("Did not encounter an error") + return + } + + _, ok := err.(ErrorList) + if !ok { + t.Errorf("response of queryer was not an error list: %v", err.Error()) + return + } + }) + + t.Run("Responds with Error", func(t *testing.T) { + // the corresponding query document + query := ` + { + hello + } + ` + + queryer := row.queryer.WithHTTPClient(&http.Client{ + Transport: roundTripFunc(func(req *http.Request) *http.Response { + // send an error back + return &http.Response{ + StatusCode: 500, + Body: ioutil.NopCloser(bytes.NewBufferString("Something went wrong")), + Header: make(http.Header), + } + }), + }) + + // get the response of the query + var result interface{} + err := queryer.Query(context.Background(), &QueryInput{Query: query}, result) + if err == nil { + t.Error("Did not receive an error") + return + } + }) + }) + } +} + +func TestQueryerWithMiddlewares(t *testing.T) { + queryerTable := []struct { + name string + queryer HTTPQueryerWithMiddlewares + wrapInList bool + }{ + { + "Single Request", + NewSingleRequestQueryer("hello"), + false, + }, + { + "MultiOp", + NewMultiOpQueryer("hello", 1*time.Millisecond, 10), + true, + }, + } + + for _, row := range queryerTable { + t.Run(row.name, func(t *testing.T) { + t.Run("Middleware Failures", func(t *testing.T) { + queryer := row.queryer.WithMiddlewares([]NetworkMiddleware{ + func(r *http.Request) error { + return errors.New("This One") + }, + }) + + // the input to the query + input := &QueryInput{ + Query: "", + } + + // fire the query + err := queryer.Query(context.Background(), input, &map[string]interface{}{}) + if err == nil { + t.Error("Did not enounter an error when we should have") + return + } + if err.Error() != "This One" { + t.Errorf("Did not encountered expected error message: Expected 'This One', found %v", err.Error()) + } + }) + + t.Run("Middlware success", func(t *testing.T) { + queryer := row.queryer.WithMiddlewares([]NetworkMiddleware{ + func(r *http.Request) error { + r.Header.Set("Hello", "World") + + return nil + }, + }) + + if q, ok := queryer.(HTTPQueryerWithMiddlewares); ok { + queryer = q.WithHTTPClient(&http.Client{ + Transport: roundTripFunc(func(req *http.Request) *http.Response { + // if we did not get the right header value + if req.Header.Get("Hello") != "World" { + return &http.Response{ + StatusCode: http.StatusExpectationFailed, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBufferString("Did not recieve the right header")), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + } + + // serialize the json we want to send back + result, _ := json.Marshal(map[string]interface{}{ + "allUsers": []string{ + "John Jacob", + "Jinglehymer Schmidt", + }, + }) + if row.wrapInList { + result = []byte(fmt.Sprintf("[%s]", string(result))) + } + + return &http.Response{ + StatusCode: 200, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBuffer(result)), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + }), + }) + } + + // the input to the query + input := &QueryInput{ + Query: "", + } + + err := queryer.Query(context.Background(), input, &map[string]interface{}{}) + if err != nil { + t.Error(err.Error()) + return + } + }) + }) + } +}