1package graphql
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "net/http"
9
10 "github.com/shurcooL/go/ctxhttp"
11 "github.com/shurcooL/graphql/internal/jsonutil"
12)
13
14// Client is a GraphQL client.
15type Client struct {
16 url string // GraphQL server URL.
17 httpClient *http.Client
18}
19
20// NewClient creates a GraphQL client targeting the specified GraphQL server URL.
21// If httpClient is nil, then http.DefaultClient is used.
22func NewClient(url string, httpClient *http.Client) *Client {
23 if httpClient == nil {
24 httpClient = http.DefaultClient
25 }
26 return &Client{
27 url: url,
28 httpClient: httpClient,
29 }
30}
31
32// Query executes a single GraphQL query request,
33// with a query derived from q, populating the response into it.
34// q should be a pointer to struct that corresponds to the GraphQL schema.
35func (c *Client) Query(ctx context.Context, q interface{}, variables map[string]interface{}) error {
36 return c.do(ctx, queryOperation, q, variables)
37}
38
39// Mutate executes a single GraphQL mutation request,
40// with a mutation derived from m, populating the response into it.
41// m should be a pointer to struct that corresponds to the GraphQL schema.
42func (c *Client) Mutate(ctx context.Context, m interface{}, variables map[string]interface{}) error {
43 return c.do(ctx, mutationOperation, m, variables)
44}
45
46// do executes a single GraphQL operation.
47func (c *Client) do(ctx context.Context, op operationType, v interface{}, variables map[string]interface{}) error {
48 var query string
49 switch op {
50 case queryOperation:
51 query = constructQuery(v, variables)
52 case mutationOperation:
53 query = constructMutation(v, variables)
54 }
55 in := struct {
56 Query string `json:"query"`
57 Variables map[string]interface{} `json:"variables,omitempty"`
58 }{
59 Query: query,
60 Variables: variables,
61 }
62 var buf bytes.Buffer
63 err := json.NewEncoder(&buf).Encode(in)
64 if err != nil {
65 return err
66 }
67 resp, err := ctxhttp.Post(ctx, c.httpClient, c.url, "application/json", &buf)
68 if err != nil {
69 return err
70 }
71 defer resp.Body.Close()
72 if resp.StatusCode != http.StatusOK {
73 return fmt.Errorf("unexpected status: %v", resp.Status)
74 }
75 var out struct {
76 Data *json.RawMessage
77 Errors errors
78 //Extensions interface{} // Unused.
79 }
80 err = json.NewDecoder(resp.Body).Decode(&out)
81 if err != nil {
82 return err
83 }
84 if out.Data != nil {
85 err := jsonutil.UnmarshalGraphQL(*out.Data, v)
86 if err != nil {
87 return err
88 }
89 }
90 if len(out.Errors) > 0 {
91 return out.Errors
92 }
93 return nil
94}
95
96// errors represents the "errors" array in a response from a GraphQL server.
97// If returned via error interface, the slice is expected to contain at least 1 element.
98//
99// Specification: https://facebook.github.io/graphql/#sec-Errors.
100type errors []struct {
101 Message string
102 Locations []struct {
103 Line int
104 Column int
105 }
106}
107
108// Error implements error interface.
109func (e errors) Error() string {
110 return e[0].Message
111}
112
113type operationType uint8
114
115const (
116 queryOperation operationType = iota
117 mutationOperation
118 //subscriptionOperation // Unused.
119)