github.com/humans-group/gqlgen@v0.7.2/client/client.go (about)

     1  // client is used internally for testing. See readme for alternatives
     2  package client
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  
    11  	"github.com/mitchellh/mapstructure"
    12  )
    13  
    14  // Client for graphql requests
    15  type Client struct {
    16  	url    string
    17  	client *http.Client
    18  }
    19  
    20  // New creates a graphql client
    21  func New(url string, client ...*http.Client) *Client {
    22  	p := &Client{
    23  		url: url,
    24  	}
    25  
    26  	if len(client) > 0 {
    27  		p.client = client[0]
    28  	} else {
    29  		p.client = http.DefaultClient
    30  	}
    31  	return p
    32  }
    33  
    34  type Request struct {
    35  	Query         string                 `json:"query"`
    36  	Variables     map[string]interface{} `json:"variables,omitempty"`
    37  	OperationName string                 `json:"operationName,omitempty"`
    38  }
    39  
    40  type Option func(r *Request)
    41  
    42  func Var(name string, value interface{}) Option {
    43  	return func(r *Request) {
    44  		if r.Variables == nil {
    45  			r.Variables = map[string]interface{}{}
    46  		}
    47  
    48  		r.Variables[name] = value
    49  	}
    50  }
    51  
    52  func Operation(name string) Option {
    53  	return func(r *Request) {
    54  		r.OperationName = name
    55  	}
    56  }
    57  
    58  func (p *Client) MustPost(query string, response interface{}, options ...Option) {
    59  	if err := p.Post(query, response, options...); err != nil {
    60  		panic(err)
    61  	}
    62  }
    63  
    64  func (p *Client) mkRequest(query string, options ...Option) Request {
    65  	r := Request{
    66  		Query: query,
    67  	}
    68  
    69  	for _, option := range options {
    70  		option(&r)
    71  	}
    72  
    73  	return r
    74  }
    75  
    76  type ResponseData struct {
    77  	Data       interface{}
    78  	Errors     json.RawMessage
    79  	Extensions map[string]interface{}
    80  }
    81  
    82  func (p *Client) Post(query string, response interface{}, options ...Option) (resperr error) {
    83  	respDataRaw, resperr := p.RawPost(query, options...)
    84  	if resperr != nil {
    85  		return resperr
    86  	}
    87  
    88  	// we want to unpack even if there is an error, so we can see partial responses
    89  	unpackErr := unpack(respDataRaw.Data, response)
    90  
    91  	if respDataRaw.Errors != nil {
    92  		return RawJsonError{respDataRaw.Errors}
    93  	}
    94  	return unpackErr
    95  }
    96  
    97  func (p *Client) RawPost(query string, options ...Option) (*ResponseData, error) {
    98  	r := p.mkRequest(query, options...)
    99  	requestBody, err := json.Marshal(r)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("encode: %s", err.Error())
   102  	}
   103  
   104  	rawResponse, err := p.client.Post(p.url, "application/json", bytes.NewBuffer(requestBody))
   105  	if err != nil {
   106  		return nil, fmt.Errorf("post: %s", err.Error())
   107  	}
   108  	defer func() {
   109  		_ = rawResponse.Body.Close()
   110  	}()
   111  
   112  	if rawResponse.StatusCode >= http.StatusBadRequest {
   113  		responseBody, _ := ioutil.ReadAll(rawResponse.Body)
   114  		return nil, fmt.Errorf("http %d: %s", rawResponse.StatusCode, responseBody)
   115  	}
   116  
   117  	responseBody, err := ioutil.ReadAll(rawResponse.Body)
   118  	if err != nil {
   119  		return nil, fmt.Errorf("read: %s", err.Error())
   120  	}
   121  
   122  	// decode it into map string first, let mapstructure do the final decode
   123  	// because it can be much stricter about unknown fields.
   124  	respDataRaw := &ResponseData{}
   125  	err = json.Unmarshal(responseBody, &respDataRaw)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("decode: %s", err.Error())
   128  	}
   129  
   130  	return respDataRaw, nil
   131  }
   132  
   133  type RawJsonError struct {
   134  	json.RawMessage
   135  }
   136  
   137  func (r RawJsonError) Error() string {
   138  	return string(r.RawMessage)
   139  }
   140  
   141  func unpack(data interface{}, into interface{}) error {
   142  	d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   143  		Result:      into,
   144  		TagName:     "json",
   145  		ErrorUnused: true,
   146  		ZeroFields:  true,
   147  	})
   148  	if err != nil {
   149  		return fmt.Errorf("mapstructure: %s", err.Error())
   150  	}
   151  
   152  	return d.Decode(data)
   153  }