|
1 | 1 | package client |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "bytes" |
5 | | - "compress/gzip" |
6 | 4 | "context" |
7 | | - "encoding/json" |
8 | | - "errors" |
9 | 5 | "fmt" |
10 | | - "io" |
11 | 6 | "net/http" |
12 | | - |
13 | | - "github.com/Yamashou/gqlgenc/v3/graphqljson" |
14 | | - "github.com/vektah/gqlparser/v2/gqlerror" |
15 | 7 | ) |
16 | 8 |
|
17 | 9 | type Client struct { |
@@ -49,181 +41,24 @@ func (c *Client) Post(ctx context.Context, operationName, query string, variable |
49 | 41 | option(client) |
50 | 42 | } |
51 | 43 |
|
52 | | - req, err := newRequest(ctx, client.endpoint, operationName, query, variables) |
53 | | - if err != nil { |
54 | | - return fmt.Errorf("failed to create post request: %w", err) |
55 | | - } |
56 | | - |
57 | | - resp, err := client.client.Do(req) |
58 | | - if err != nil { |
59 | | - return fmt.Errorf("request failed: %w", err) |
60 | | - } |
61 | | - defer resp.Body.Close() |
62 | | - |
63 | | - return client.parseResponse(resp, out) |
64 | | -} |
65 | | - |
66 | | -// PostMultipart send multipart form with files https://gqlgen.com/reference/file-upload/ https://github.com/jaydenseric/graphql-multipart-request-spec |
67 | | -func (c *Client) PostMultipart(ctx context.Context, operationName, query string, variables map[string]any, out any, options ...Option) error { |
68 | | - client := &Client{ |
69 | | - client: c.client, |
70 | | - endpoint: c.endpoint, |
71 | | - } |
72 | | - for _, option := range options { |
73 | | - option(client) |
74 | | - } |
75 | | - |
76 | | - req, err := multipartRequest(ctx, client.endpoint, operationName, query, variables) |
| 44 | + // PostMultipart send multipart form with files https://gqlgen.com/reference/file-upload/ https://github.com/jaydenseric/graphql-multipart-request-spec |
| 45 | + req, err := NewMultipartRequest(ctx, client.endpoint, operationName, query, variables) |
77 | 46 | if err != nil { |
78 | 47 | return fmt.Errorf("failed to create post multipart request: %w", err) |
79 | 48 | } |
80 | 49 |
|
81 | | - resp, err := client.client.Do(req) |
82 | | - if err != nil { |
83 | | - return fmt.Errorf("request failed: %w", err) |
84 | | - } |
85 | | - defer resp.Body.Close() |
86 | | - |
87 | | - return client.parseResponse(resp, out) |
88 | | -} |
89 | | - |
90 | | -///////////////////////////////////////////////////////////////////// |
91 | | -// Request |
92 | | - |
93 | | -// Request represents an outgoing GraphQL request |
94 | | -type Request struct { |
95 | | - Query string `json:"query"` |
96 | | - Variables map[string]any `json:"variables,omitempty"` |
97 | | - OperationName string `json:"operationName,omitempty"` |
98 | | -} |
99 | | - |
100 | | -func newRequest(ctx context.Context, endpoint, operationName, query string, variables map[string]any) (*http.Request, error) { |
101 | | - graphqlRequest := &Request{ |
102 | | - Query: query, |
103 | | - Variables: variables, |
104 | | - OperationName: operationName, |
105 | | - } |
106 | | - requestBody, err := json.Marshal(graphqlRequest) |
107 | | - if err != nil { |
108 | | - return nil, fmt.Errorf("encode: %w", err) |
109 | | - } |
110 | | - |
111 | | - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(requestBody)) |
112 | | - if err != nil { |
113 | | - return nil, fmt.Errorf("create request struct failed: %w", err) |
114 | | - } |
115 | | - |
116 | | - req.Header = http.Header{ |
117 | | - "Content-Type": []string{"application/graphql-response+json;charset=utf-8", "application/json; charset=utf-8"}, |
118 | | - "Accept": []string{"application/graphql-response+json;charset=utf-8", "application/json; charset=utf-8"}, |
119 | | - } |
120 | | - |
121 | | - return req, nil |
122 | | -} |
123 | | - |
124 | | -///////////////////////////////////////////////////////////////////// |
125 | | -// Response |
126 | | - |
127 | | -// httpError is the error when a gqlErrors cannot be parsed |
128 | | -type httpError struct { |
129 | | - Code int `json:"code"` |
130 | | - Message string `json:"message"` |
131 | | -} |
132 | | - |
133 | | -// gqlErrors is the struct of a standard graphql error response |
134 | | -type gqlErrors struct { |
135 | | - Errors gqlerror.List `json:"errors"` |
136 | | -} |
137 | | - |
138 | | -func (e *gqlErrors) Error() string { |
139 | | - return e.Errors.Error() |
140 | | -} |
141 | | - |
142 | | -// errorResponse represent an handled error |
143 | | -type errorResponse struct { |
144 | | - // http status code is not OK |
145 | | - NetworkError *httpError `json:"networkErrors"` |
146 | | - // http status code is OK but the server returned at least one graphql error |
147 | | - GqlErrors *gqlerror.List `json:"graphqlErrors"` |
148 | | -} |
149 | | - |
150 | | -// HasErrors returns true when at least one error is declared |
151 | | -func (er *errorResponse) HasErrors() bool { |
152 | | - return er.NetworkError != nil || er.GqlErrors != nil |
153 | | -} |
154 | | - |
155 | | -func (er *errorResponse) Error() string { |
156 | | - content, err := json.Marshal(er) |
157 | | - if err != nil { |
158 | | - return err.Error() |
159 | | - } |
160 | | - |
161 | | - return string(content) |
162 | | -} |
163 | | - |
164 | | -func (c *Client) parseResponse(resp *http.Response, out any) error { |
165 | | - if resp.Header.Get("Content-Encoding") == "gzip" { |
166 | | - respBody, err := gzip.NewReader(resp.Body) |
| 50 | + if req == nil { |
| 51 | + req, err = NewRequest(ctx, client.endpoint, operationName, query, variables) |
167 | 52 | if err != nil { |
168 | | - return fmt.Errorf("failed to decode gzip: %w", err) |
| 53 | + return fmt.Errorf("failed to create post request: %w", err) |
169 | 54 | } |
170 | | - resp.Body = respBody |
171 | 55 | } |
172 | 56 |
|
173 | | - body, err := io.ReadAll(resp.Body) |
| 57 | + resp, err := client.client.Do(req) |
174 | 58 | if err != nil { |
175 | | - return fmt.Errorf("failed to read response body: %w", err) |
176 | | - } |
177 | | - |
178 | | - errResponse := &errorResponse{} |
179 | | - isStatusCodeOK := 200 <= resp.StatusCode && resp.StatusCode <= 299 |
180 | | - if !isStatusCodeOK { |
181 | | - errResponse.NetworkError = &httpError{ |
182 | | - Code: resp.StatusCode, |
183 | | - Message: fmt.Sprintf("Response body %s", string(body)), |
184 | | - } |
185 | | - } |
186 | | - |
187 | | - if err := c.unmarshalResponse(body, out); err != nil { |
188 | | - var gqlErrs *gqlErrors |
189 | | - if errors.As(err, &gqlErrs) { |
190 | | - // success to parse graphql error response |
191 | | - errResponse.GqlErrors = &gqlErrs.Errors |
192 | | - } else if isStatusCodeOK { |
193 | | - // status code is OK but the GraphQL response can't be parsed, it's an error. |
194 | | - return fmt.Errorf("http status is OK but %w", err) |
195 | | - } |
196 | | - } |
197 | | - |
198 | | - if errResponse.HasErrors() { |
199 | | - return errResponse |
200 | | - } |
201 | | - |
202 | | - return nil |
203 | | -} |
204 | | - |
205 | | -// response is a GraphQL layer response from a handler. |
206 | | -type response struct { |
207 | | - Data json.RawMessage `json:"data"` |
208 | | - Errors json.RawMessage `json:"errors"` |
209 | | -} |
210 | | - |
211 | | -func (c *Client) unmarshalResponse(respBody []byte, out any) error { |
212 | | - resp := response{} |
213 | | - if err := json.Unmarshal(respBody, &resp); err != nil { |
214 | | - return fmt.Errorf("failed to decode response %q: %w", respBody, err) |
215 | | - } |
216 | | - if err := graphqljson.UnmarshalData(resp.Data, out); err != nil { |
217 | | - return fmt.Errorf("failed to decode response data %q: %w", resp.Data, err) |
218 | | - } |
219 | | - |
220 | | - if len(resp.Errors) > 0 { |
221 | | - gqlErrs := &gqlErrors{} |
222 | | - if err := json.Unmarshal(respBody, gqlErrs); err != nil { |
223 | | - return fmt.Errorf("faild to decode response error %q: %w", respBody, err) |
224 | | - } |
225 | | - return gqlErrs |
| 59 | + return fmt.Errorf("request failed: %w", err) |
226 | 60 | } |
| 61 | + defer resp.Body.Close() |
227 | 62 |
|
228 | | - return nil |
| 63 | + return ParseResponse(resp, out) |
229 | 64 | } |
0 commit comments