package graphql import ( "bytes" "context" "encoding/json" "fmt" "net/http" "reflect" "github.com/shurcooL/go/ctxhttp" "github.com/shurcooL/graphql/internal/jsonutil" ) // Client is a GraphQL client. type Client struct { url string // GraphQL server URL. httpClient *http.Client qctx *queryContext } // NewClient creates a GraphQL client targeting the specified GraphQL server URL. // If httpClient is nil, then http.DefaultClient is used. // scalars optionally specifies types that are scalars (this matters // when constructing queries from types, scalars are never expanded). func NewClient(url string, httpClient *http.Client, scalars []reflect.Type) *Client { if httpClient == nil { httpClient = http.DefaultClient } return &Client{ url: url, httpClient: httpClient, qctx: &queryContext{ Scalars: scalars, }, } } // 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) } // 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) } // do executes a single GraphQL operation. func (c *Client) do(ctx context.Context, op operationType, v interface{}, variables map[string]interface{}) error { var query string switch op { case queryOperation: query = constructQuery(c.qctx, v, variables) case mutationOperation: query = constructMutation(c.qctx, v, variables) } in := struct { Query string `json:"query"` Variables map[string]interface{} `json:"variables,omitempty"` }{ Query: query, Variables: variables, } var buf bytes.Buffer err := json.NewEncoder(&buf).Encode(in) if err != nil { return err } resp, err := ctxhttp.Post(ctx, c.httpClient, c.url, "application/json", &buf) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status: %v", resp.Status) } var out struct { Data json.RawMessage Errors errors //Extensions interface{} // Unused. } err = json.NewDecoder(resp.Body).Decode(&out) if err != nil { return err } if len(out.Errors) > 0 { return out.Errors } err = jsonutil.UnmarshalGraphQL(out.Data, v) return err } // 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 string Locations []struct { Line int Column int } } // Error implements error interface. func (e errors) Error() string { return e[0].Message } type operationType uint8 const ( queryOperation operationType = iota mutationOperation //subscriptionOperation // Unused. )