Skip to content

Commit db1b8e5

Browse files
committed
Add Client type, remove ctxhttp dependency.
ctxhttp is only useful if you want to support Go <1.7. I removed it because this package already had a dependency on the stdlib context package.
1 parent b02d64d commit db1b8e5

File tree

6 files changed

+90
-131
lines changed

6 files changed

+90
-131
lines changed

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ Low-level GraphQL client for Go.
1010
* Simple error handling
1111

1212
```go
13+
// create a client (safe to share across requests)
14+
ctx := context.Background()
15+
client, err := graphql.NewClient(ctx, "https://machinebox.io/graphql")
16+
if err != nil {
17+
log.Fatal(err)
18+
}
19+
1320
// make a request
1421
req := graphql.NewRequest(`
1522
query ($key: String!) {
@@ -24,13 +31,9 @@ req := graphql.NewRequest(`
2431
// set any variables
2532
req.Var("key", "value")
2633

27-
// get a context
28-
ctx := context.Background()
29-
ctx := graphql.NewContext(ctx, "https://machinebox.io/graphql")
30-
3134
// run it and capture the response
3235
var respData ResponseStruct
33-
if err := req.Run(ctx, &respData); err != nil {
34-
log.Fatalln(err)
36+
if err := client.Run(ctx, req, &respData); err != nil {
37+
log.Fatal(err)
3538
}
3639
```

client.go

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,41 @@ import (
1010
"net/http"
1111

1212
"github.com/pkg/errors"
13-
"golang.org/x/net/context/ctxhttp"
1413
)
1514

16-
// Client accesses a GraphQL API.
17-
type client struct {
18-
endpoint string
15+
// Client is a client for accessing a GraphQL dataset.
16+
type Client struct {
17+
endpoint string
18+
httpClient *http.Client
1919
}
2020

21-
// Do executes a query request and returns the response.
22-
func (c *client) Do(ctx context.Context, request *Request, response interface{}) error {
23-
if err := ctx.Err(); err != nil {
24-
return err
21+
type ClientOption interface {
22+
apply(*Client)
23+
}
24+
25+
func NewClient(ctx context.Context, endpoint string, opts ...ClientOption) (*Client, error) {
26+
c := &Client{
27+
endpoint: endpoint,
28+
}
29+
for _, o := range opts {
30+
o.apply(c)
31+
}
32+
if c.httpClient == nil {
33+
c.httpClient = http.DefaultClient
34+
}
35+
return c, nil
36+
}
37+
38+
// Run executes the query and unmarshals the response from the data field
39+
// into the response object.
40+
// Pass in a nil response object to skip response parsing.
41+
// If the request fails or the server returns an error, the first error
42+
// will be returned. Use IsGraphQLErr to determine which it was.
43+
func (c *Client) Run(ctx context.Context, request *Request, response interface{}) error {
44+
select {
45+
case <-ctx.Done():
46+
return ctx.Err()
47+
default:
2548
}
2649
var requestBody bytes.Buffer
2750
writer := multipart.NewWriter(&requestBody)
@@ -66,11 +89,8 @@ func (c *client) Do(ctx context.Context, request *Request, response interface{})
6689
}
6790
req.Header.Set("Content-Type", writer.FormDataContentType())
6891
req.Header.Set("Accept", "application/json")
69-
client, ok := ctx.Value(httpclientContextKey).(*http.Client)
70-
if !ok {
71-
client = http.DefaultClient
72-
}
73-
res, err := ctxhttp.Do(ctx, client, req)
92+
req = req.WithContext(ctx)
93+
res, err := c.httpClient.Do(req)
7494
if err != nil {
7595
return err
7696
}
@@ -89,9 +109,17 @@ func (c *client) Do(ctx context.Context, request *Request, response interface{})
89109
return nil
90110
}
91111

92-
// WithClient specifies the http.Client that requests will use.
93-
func WithClient(ctx context.Context, client *http.Client) context.Context {
94-
return context.WithValue(ctx, httpclientContextKey, client)
112+
type httpClientOption struct {
113+
hc *http.Client
114+
}
115+
116+
func (o httpClientOption) apply(c *Client) {
117+
c.httpClient = o.hc
118+
}
119+
120+
// WithHTTPClient specifies the http.Client that requests will use.
121+
func WithHTTPClient(client *http.Client) ClientOption {
122+
return httpClientOption{client}
95123
}
96124

97125
type graphErr struct {
@@ -117,19 +145,6 @@ func NewRequest(q string) *Request {
117145
return req
118146
}
119147

120-
// Run executes the query and unmarshals the response from the data field
121-
// into the response object.
122-
// Pass in a nil response object to skip response parsing.
123-
// If the request fails or the server returns an error, the first error
124-
// will be returned. Use IsGraphQLErr to determine which it was.
125-
func (req *Request) Run(ctx context.Context, response interface{}) error {
126-
client, err := fromContext(ctx)
127-
if err != nil {
128-
return err
129-
}
130-
return client.Do(ctx, req, response)
131-
}
132-
133148
// Var sets a variable.
134149
func (req *Request) Var(key string, value interface{}) {
135150
if req.vars == nil {

client_test.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ func TestWithClient(t *testing.T) {
2626
}),
2727
}
2828

29-
ctx := NewContext(context.Background(), "")
30-
ctx = WithClient(ctx, testClient)
29+
ctx := context.Background()
30+
client, err := NewClient(ctx, "", WithHTTPClient(testClient))
31+
is.NoErr(err)
3132

3233
req := NewRequest(``)
33-
req.Run(ctx, nil)
34+
client.Run(ctx, req, nil)
3435

3536
is.Equal(calls, 1) // calls
3637
}
@@ -50,14 +51,15 @@ func TestDo(t *testing.T) {
5051
}`)
5152
}))
5253
defer srv.Close()
53-
c := &client{
54-
endpoint: srv.URL,
55-
}
54+
5655
ctx := context.Background()
56+
client, err := NewClient(ctx, srv.URL)
57+
is.NoErr(err)
58+
5759
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
5860
defer cancel()
5961
var responseData map[string]interface{}
60-
err := c.Do(ctx, &Request{q: "query {}"}, &responseData)
62+
err = client.Run(ctx, &Request{q: "query {}"}, &responseData)
6163
is.NoErr(err)
6264
is.Equal(calls, 1) // calls
6365
is.Equal(responseData["something"], "yes")
@@ -78,14 +80,15 @@ func TestDoErr(t *testing.T) {
7880
}`)
7981
}))
8082
defer srv.Close()
81-
c := &client{
82-
endpoint: srv.URL,
83-
}
83+
8484
ctx := context.Background()
85+
client, err := NewClient(ctx, srv.URL)
86+
is.NoErr(err)
87+
8588
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
8689
defer cancel()
8790
var responseData map[string]interface{}
88-
err := c.Do(ctx, &Request{q: "query {}"}, &responseData)
91+
err = client.Run(ctx, &Request{q: "query {}"}, &responseData)
8992
is.True(err != nil)
9093
is.Equal(err.Error(), "graphql: Something went wrong")
9194
}
@@ -105,13 +108,14 @@ func TestDoNoResponse(t *testing.T) {
105108
}`)
106109
}))
107110
defer srv.Close()
108-
c := &client{
109-
endpoint: srv.URL,
110-
}
111+
111112
ctx := context.Background()
113+
client, err := NewClient(ctx, srv.URL)
114+
is.NoErr(err)
115+
112116
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
113117
defer cancel()
114-
err := c.Do(ctx, &Request{q: "query {}"}, nil)
118+
err = client.Run(ctx, &Request{q: "query {}"}, nil)
115119
is.NoErr(err)
116120
is.Equal(calls, 1) // calls
117121
}
@@ -131,7 +135,9 @@ func TestQuery(t *testing.T) {
131135
defer srv.Close()
132136
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
133137
defer cancel()
134-
ctx = NewContext(ctx, srv.URL)
138+
139+
client, err := NewClient(ctx, srv.URL)
140+
is.NoErr(err)
135141

136142
req := NewRequest("query {}")
137143
req.Var("username", "matryer")
@@ -143,7 +149,7 @@ func TestQuery(t *testing.T) {
143149
var resp struct {
144150
Value string
145151
}
146-
err := req.Run(ctx, &resp)
152+
err = client.Run(ctx, req, &resp)
147153
is.NoErr(err)
148154
is.Equal(calls, 1)
149155

@@ -173,16 +179,17 @@ func TestFile(t *testing.T) {
173179
defer srv.Close()
174180
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
175181
defer cancel()
176-
ctx = NewContext(ctx, srv.URL)
182+
183+
client, err := NewClient(ctx, srv.URL)
184+
is.NoErr(err)
177185

178186
f := strings.NewReader(`This is a file`)
179187

180188
req := NewRequest("query {}")
181189
req.File("filename.txt", f)
182190

183-
err := req.Run(ctx, nil)
191+
err = client.Run(ctx, req, nil)
184192
is.NoErr(err)
185-
186193
}
187194

188195
type roundTripperFunc func(req *http.Request) (*http.Response, error)

context.go

Lines changed: 0 additions & 40 deletions
This file was deleted.

context_test.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

doc.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
// Package graphql provides a low level GraphQL client.
2+
//
3+
// // create a client (safe to share across requests)
4+
// ctx := context.Background()
5+
// client, err := graphql.NewClient(ctx, "https://machinebox.io/graphql")
6+
// if err != nil {
7+
// log.Fatal(err)
8+
// }
9+
//
210
// // make a request
311
// req := graphql.NewRequest(`
412
// query ($key: String!) {
@@ -13,13 +21,9 @@
1321
// // set any variables
1422
// req.Var("key", "value")
1523
//
16-
// // get a context
17-
// ctx := context.Background()
18-
// ctx := graphql.NewContext(ctx, "https://machinebox.io/graphql")
19-
//
2024
// // run it and capture the response
2125
// var respData ResponseStruct
22-
// if err := req.Run(ctx, &respData); err != nil {
23-
// log.Fatalln(err)
26+
// if err := client.Run(ctx, req, &respData); err != nil {
27+
// log.Fatal(err)
2428
// }
2529
package graphql

0 commit comments

Comments
 (0)