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 }