Skip to content

Commit

Permalink
Allow headers through a new API
Browse files Browse the repository at this point in the history
  • Loading branch information
rideliner committed Apr 30, 2020
1 parent d48a9a7 commit 092e75a
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 336 deletions.
105 changes: 0 additions & 105 deletions example/graphqldev/main.go

This file was deleted.

65 changes: 34 additions & 31 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,38 +34,53 @@ func NewClient(url string, httpClient *http.Client) *Client {
// 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)
return c.Run(ctx, &Query{
Data: q,
Vars: 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)
return c.Run(ctx, &Mutation{
Data: m,
Vars: variables,
})
}

type request struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables,omitempty"`
OperationName string `json:"operationName,omitempty"`
}

type response struct {
Data *json.RawMessage
Errors errors
//Extensions interface{} // Unused.
}

// 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(v, variables)
case mutationOperation:
query = constructMutation(v, variables)
}
in := struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables,omitempty"`
}{
Query: query,
Variables: variables,
func (c *Client) Run(ctx context.Context, op Operation) error {
in := request{
Query: op.Query(),
Variables: op.Variables(),
OperationName: op.OperationName(),
}
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)
req, err := http.NewRequest(http.MethodPost, c.url, &buf)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
op.ModifyRequest(req)
resp, err := ctxhttp.Do(ctx, c.httpClient, req)
if err != nil {
return err
}
Expand All @@ -74,18 +89,14 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("non-200 OK status code: %v body: %q", resp.Status, body)
}
var out struct {
Data *json.RawMessage
Errors errors
//Extensions interface{} // Unused.
}
var out response
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
// TODO: Consider including response body in returned error, if deemed helpful.
return err
}
if out.Data != nil {
err := jsonutil.UnmarshalGraphQL(*out.Data, v)
err := jsonutil.UnmarshalGraphQL(*out.Data, op.ResponsePtr())
if err != nil {
// TODO: Consider including response body in returned error, if deemed helpful.
return err
Expand Down Expand Up @@ -113,11 +124,3 @@ type errors []struct {
func (e errors) Error() string {
return e[0].Message
}

type operationType uint8

const (
queryOperation operationType = iota
mutationOperation
//subscriptionOperation // Unused.
)
147 changes: 137 additions & 10 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,153 @@ import (
"bytes"
"encoding/json"
"io"
"net/http"
"reflect"
"sort"
"strings"

"github.com/shurcooL/graphql/ident"
)

func constructQuery(v interface{}, variables map[string]interface{}) string {
query := query(v)
if len(variables) > 0 {
return "query(" + queryArguments(variables) + ")" + query
type RequestHandlerFunc func(req *http.Request)

type Operation interface {
Query() string
Variables() map[string]interface{}
ResponsePtr() interface{}

// This will become the `operationName` in the body of the GraphQL request.
// Since the `operationName` is only required if more than one operation is sent,
// the tendency should be to return an empty string when there is only one operation in the query.
OperationName() string

ModifyRequest(req *http.Request)
}

type Query struct {
Name string
Data interface{}
Vars map[string]interface{}

RequestHandler RequestHandlerFunc
}

func (op *Query) Query() string {
var str strings.Builder

if op.Name != "" {
str.WriteString("query ")
str.WriteString(op.Name)
}

if len(op.Vars) > 0 {
if str.Len() == 0 {
str.WriteString("query")
}
str.WriteString("(")
str.WriteString(queryArguments(op.Vars))
str.WriteString(")")
}

str.WriteString(query(op.Data))

return str.String()
}

func (op *Query) OperationName() string {
return "" // we embed the name in the query, not in the request
}

func (op *Query) Variables() map[string]interface{} {
return op.Vars
}

func (op *Query) ModifyRequest(req *http.Request) {
if op.RequestHandler != nil {
op.RequestHandler(req)
}
return query
}

func constructMutation(v interface{}, variables map[string]interface{}) string {
query := query(v)
if len(variables) > 0 {
return "mutation(" + queryArguments(variables) + ")" + query
func (op *Query) ResponsePtr() interface{} {
return op.Data
}

type Mutation struct {
Name string
Data interface{}
Vars map[string]interface{}

RequestHandler RequestHandlerFunc
}

func (op *Mutation) Query() string {
var str strings.Builder

str.WriteString("mutation")

if op.Name != "" {
str.WriteString(" ")
str.WriteString(op.Name)
}

if len(op.Vars) > 0 {
str.WriteString("(")
str.WriteString(queryArguments(op.Vars))
str.WriteString(")")
}

str.WriteString(query(op.Data))

return str.String()
}

func (op *Mutation) OperationName() string {
return "" // we embed the name in the query, not in the request
}

func (op *Mutation) Variables() map[string]interface{} {
return op.Vars
}

func (op *Mutation) ModifyRequest(req *http.Request) {
if op.RequestHandler != nil {
op.RequestHandler(req)
}
}

func (op *Mutation) ResponsePtr() interface{} {
return op.Data
}

type Static struct {
Name string
QueryStr string
Into interface{}
Vars map[string]interface{}

RequestHandler RequestHandlerFunc
}

func (op *Static) Query() string {
return op.QueryStr
}

func (op *Static) OperationName() string {
return op.Name
}

func (op *Static) Variables() map[string]interface{} {
return op.Vars
}

func (op *Static) ResponsePtr() interface{} {
return op.Into
}

func (op *Static) ModifyRequest(req *http.Request) {
if op.RequestHandler != nil {
op.RequestHandler(req)
}
return "mutation" + query
}

// queryArguments constructs a minified arguments string for variables.
Expand Down
Loading

0 comments on commit 092e75a

Please sign in to comment.