diff --git a/.travis.yml b/.travis.yml index 93b1fcd..12607ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,5 +12,5 @@ install: script: - go get -t -v ./... - diff -u <(echo -n) <(gofmt -d -s .) - - go tool vet . + - go vet . - go test -v -race ./... diff --git a/graphql.go b/graphql.go index 8520956..bcdb520 100644 --- a/graphql.go +++ b/graphql.go @@ -30,22 +30,34 @@ func NewClient(url string, httpClient *http.Client) *Client { } } +// RequestOption is a variadic option for modifying an underlying HTTP request +// for GraphQL. +type RequestOption func(req *http.Request) error + +// WithRequestHeader sets an explicit HTTP header for usage +func WithRequestHeader(key, value string) RequestOption { + return func(req *http.Request) error { + req.Header.Set(key, value) + return nil + } +} + // Query executes a single GraphQL query request, // with a query derived from q, populating the response into it. // q should be a pointer to struct that corresponds to the GraphQL schema. -func (c *Client) Query(ctx context.Context, q interface{}, variables map[string]interface{}) error { - return c.do(ctx, queryOperation, q, variables) +func (c *Client) Query(ctx context.Context, q interface{}, variables map[string]interface{}, opts ...RequestOption) error { + return c.do(ctx, queryOperation, q, variables, opts...) } // Mutate executes a single GraphQL mutation request, // with a mutation derived from m, populating the response into it. // m should be a pointer to struct that corresponds to the GraphQL schema. -func (c *Client) Mutate(ctx context.Context, m interface{}, variables map[string]interface{}) error { - return c.do(ctx, mutationOperation, m, variables) +func (c *Client) Mutate(ctx context.Context, m interface{}, variables map[string]interface{}, opts ...RequestOption) error { + return c.do(ctx, mutationOperation, m, variables, opts...) } // do executes a single GraphQL operation. -func (c *Client) do(ctx context.Context, op operationType, v interface{}, variables map[string]interface{}) error { +func (c *Client) do(ctx context.Context, op operationType, v interface{}, variables map[string]interface{}, opts ...RequestOption) error { var query string switch op { case queryOperation: @@ -65,7 +77,20 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab if err != nil { return err } - resp, err := ctxhttp.Post(ctx, c.httpClient, c.url, "application/json", &buf) + req, err := http.NewRequest(http.MethodPost, c.url, &buf) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + for _, opt := range opts { + err := opt(req) + if err != nil { + return err + } + } + + resp, err := ctxhttp.Do(ctx, c.httpClient, req) if err != nil { return err } diff --git a/graphql_test.go b/graphql_test.go index e09dcc9..c04a5f0 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -153,6 +153,30 @@ func TestClient_Query_emptyVariables(t *testing.T) { } } +func TestClient_Query_requestOptions(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/graphql", func(w http.ResponseWriter, req *http.Request) { + header := req.Header.Get("Custom-Request-Header") + if got, want := header, "test-custom-header"; got != want { + t.Errorf("got header: %v, want %v", got, want) + } + + w.Header().Set("Content-Type", "application/json") + mustWrite(w, `{"data": {"user": {"name": "Gopher"}}}`) + }) + client := graphql.NewClient("/graphql", &http.Client{Transport: localRoundTripper{handler: mux}}) + + var q struct { + User struct { + Name string + } + } + err := client.Query(context.Background(), &q, map[string]interface{}{}, graphql.WithRequestHeader("Custom-Request-Header", "test-custom-header")) + if err != nil { + t.Fatal(err) + } +} + // localRoundTripper is an http.RoundTripper that executes HTTP transactions // by using handler directly, instead of going over an HTTP connection. type localRoundTripper struct {