@@ -10,6 +10,7 @@ import (
1010 "io"
1111 "net/http"
1212 "net/url"
13+ "syscall"
1314 "time"
1415
1516 "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
@@ -154,6 +155,20 @@ func WithJSONResponse(response interface{}) DoOption {
154155 }
155156}
156157
158+ // Ignore content type header and always try to parse the response as JSON.
159+ func WithAlwaysJSONResponse (response interface {}) DoOption {
160+ return func (resp * WrapperResponse ) error {
161+ if response == nil && len (resp .Body ) == 0 {
162+ return nil
163+ }
164+ err := json .Unmarshal (resp .Body , response )
165+ if err != nil {
166+ return fmt .Errorf ("failed to unmarshal json response: %w. status code: %d. body %v" , err , resp .StatusCode , logBody (resp .Body , maxBodySize ))
167+ }
168+ return nil
169+ }
170+ }
171+
157172func logBody (body []byte , size int ) string {
158173 if len (body ) > size {
159174 return string (body [:size ]) + " ..."
@@ -234,6 +249,17 @@ func WithResponse(response interface{}) DoOption {
234249 }
235250}
236251
252+ func WrapErrors (preferredCode codes.Code , statusMsg string , errs ... error ) error {
253+ st := status .New (preferredCode , statusMsg )
254+
255+ if len (errs ) == 0 {
256+ return st .Err ()
257+ }
258+
259+ allErrs := append ([]error {st .Err ()}, errs ... )
260+ return errors .Join (allErrs ... )
261+ }
262+
237263func WrapErrorsWithRateLimitInfo (preferredCode codes.Code , resp * http.Response , errs ... error ) error {
238264 st := status .New (preferredCode , resp .Status )
239265
@@ -281,7 +307,10 @@ func (c *BaseHttpClient) Do(req *http.Request, options ...DoOption) (*http.Respo
281307 var urlErr * url.Error
282308 if errors .As (err , & urlErr ) {
283309 if urlErr .Timeout () {
284- return nil , status .Error (codes .DeadlineExceeded , fmt .Sprintf ("request timeout: %v" , urlErr .URL ))
310+ return nil , WrapErrors (codes .DeadlineExceeded , fmt .Sprintf ("request timeout: %v" , urlErr .URL ), urlErr )
311+ }
312+ if urlErr .Temporary () {
313+ return nil , WrapErrors (codes .Unavailable , fmt .Sprintf ("temporary error: %v" , urlErr .URL ), urlErr )
285314 }
286315 }
287316 if errors .Is (err , context .DeadlineExceeded ) {
@@ -297,6 +326,13 @@ func (c *BaseHttpClient) Do(req *http.Request, options ...DoOption) (*http.Respo
297326 if len (body ) > 0 {
298327 resp .Body = io .NopCloser (bytes .NewBuffer (body ))
299328 }
329+ // Turn certain body read errors into grpc statuses so we retry
330+ if errors .Is (err , io .ErrUnexpectedEOF ) {
331+ return resp , WrapErrors (codes .Unavailable , "unexpected EOF" , err )
332+ }
333+ if errors .Is (err , syscall .ECONNRESET ) {
334+ return resp , WrapErrors (codes .Unavailable , "connection reset" , err )
335+ }
300336 return resp , err
301337 }
302338
0 commit comments