Codebase list golang-github-go-kit-kit / 0235c32
transport/http: upgrade error encoder - Take a context.Context as a first parameter, in case there is some necessary information there (h/t @thomshutt) - Refactor BadRequestError as a TransportError - Improve defaultErrorEncoder behavior - Update tests Peter Bourgon 8 years ago
4 changed file(s) with 32 addition(s) and 58 deletion(s). Raw diff Collapse all Expand all
33 "fmt"
44 )
55
6 // These are some pre-generated constants that can be used to check against
7 // for the DomainErrors.
86 const (
9 // DomainNewRequest represents an error at the Request Generation
10 // Scope.
7 // DomainNewRequest is an error during request generation.
118 DomainNewRequest = "NewRequest"
129
13 // DomainEncode represent an error that has occurred at the Encode
14 // level of the request.
10 // DomainEncode is an error during request or response encoding.
1511 DomainEncode = "Encode"
1612
17 // DomainDo represents an error that has occurred at the Do, or
18 // execution phase of the request.
13 // DomainDo is an error during the execution phase of the request.
1914 DomainDo = "Do"
2015
21 // DomainDecode represents an error that has occurred at the Decode
22 // phase of the request.
16 // DomainDecode is an error during request or response decoding.
2317 DomainDecode = "Decode"
2418 )
2519
26 // TransportError represents an Error occurred in the Client transport level.
20 // TransportError is an error that occurred at some phase within the transport.
2721 type TransportError struct {
28 // Domain represents the domain of the error encountered.
29 // Simply, this refers to the phase in which the error was
30 // generated
22 // Domain is the phase in which the error was generated.
3123 Domain string
3224
33 // Err references the underlying error that caused this error
34 // overall.
25 // Err is the concrete error.
3526 Err error
3627 }
3728
38 // Error implements the error interface
29 // Error implements the error interface.
3930 func (e TransportError) Error() string {
4031 return fmt.Sprintf("%s: %s", e.Domain, e.Err)
4132 }
4848
4949 func ExampleErrOutput() {
5050 sampleErr := errors.New("Oh no, an error")
51 err := httptransport.TransportError{"Do", sampleErr}
51 err := httptransport.TransportError{Domain: httptransport.DomainDo, Err: sampleErr}
5252 fmt.Println(err)
5353 // Output:
5454 // Do: Oh no, an error
1616 enc EncodeResponseFunc
1717 before []RequestFunc
1818 after []ResponseFunc
19 errorEncoder func(w http.ResponseWriter, err error)
19 errorEncoder ErrorEncoder
2020 logger log.Logger
2121 }
2222
6363 // use this to provide custom error formatting and response codes. By default,
6464 // errors will be written as plain text with an appropriate, if generic,
6565 // status code.
66 func ServerErrorEncoder(f func(w http.ResponseWriter, err error)) ServerOption {
67 return func(s *Server) { s.errorEncoder = f }
66 func ServerErrorEncoder(ee ErrorEncoder) ServerOption {
67 return func(s *Server) { s.errorEncoder = ee }
6868 }
6969
7070 // ServerErrorLogger is used to log non-terminal errors. By default, no errors
8585 request, err := s.dec(r)
8686 if err != nil {
8787 s.logger.Log("err", err)
88 s.errorEncoder(w, BadRequestError{err})
88 s.errorEncoder(ctx, TransportError{Domain: DomainDecode, Err: err}, w)
8989 return
9090 }
9191
9292 response, err := s.e(ctx, request)
9393 if err != nil {
9494 s.logger.Log("err", err)
95 s.errorEncoder(w, err)
95 s.errorEncoder(ctx, TransportError{Domain: DomainDo, Err: err}, w)
9696 return
9797 }
9898
102102
103103 if err := s.enc(w, response); err != nil {
104104 s.logger.Log("err", err)
105 s.errorEncoder(w, err)
105 s.errorEncoder(ctx, TransportError{Domain: DomainEncode, Err: err}, w)
106106 return
107107 }
108108 }
109109
110 func defaultErrorEncoder(w http.ResponseWriter, err error) {
111 switch err.(type) {
112 case BadRequestError:
113 http.Error(w, err.Error(), http.StatusBadRequest)
110 // ErrorEncoder is a function that's responsible for encoding an error to the ResponseWriter.
111 type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
112
113 func defaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) {
114 switch e := err.(type) {
115 case TransportError:
116 switch e.Domain {
117 case DomainDecode:
118 http.Error(w, err.Error(), http.StatusBadRequest)
119 case DomainDo:
120 http.Error(w, err.Error(), http.StatusServiceUnavailable) // too aggressive?
121 default:
122 http.Error(w, err.Error(), http.StatusInternalServerError)
123 }
114124 default:
115125 http.Error(w, err.Error(), http.StatusInternalServerError)
116126 }
117127 }
118
119 // BadRequestError is an error in decoding the request.
120 type BadRequestError struct {
121 Err error
122 }
123
124 // Error implements the error interface.
125 func (err BadRequestError) Error() string {
126 return err.Err.Error()
127 }
3636 server := httptest.NewServer(handler)
3737 defer server.Close()
3838 resp, _ := http.Get(server.URL)
39 if want, have := http.StatusInternalServerError, resp.StatusCode; want != have {
39 if want, have := http.StatusServiceUnavailable, resp.StatusCode; want != have {
4040 t.Errorf("want %d, have %d", want, have)
4141 }
4242 }
5959 func TestServerErrorEncoder(t *testing.T) {
6060 errTeapot := errors.New("teapot")
6161 code := func(err error) int {
62 if err == errTeapot {
62 if e, ok := err.(httptransport.TransportError); ok && e.Err == errTeapot {
6363 return http.StatusTeapot
6464 }
6565 return http.StatusInternalServerError
6969 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot },
7070 func(*http.Request) (interface{}, error) { return struct{}{}, nil },
7171 func(http.ResponseWriter, interface{}) error { return nil },
72 httptransport.ServerErrorEncoder(func(w http.ResponseWriter, err error) { w.WriteHeader(code(err)) }),
72 httptransport.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }),
7373 )
7474 server := httptest.NewServer(handler)
7575 defer server.Close()
117117 }()
118118 return cancelfn, func() { stepch <- true }, response
119119 }
120
121 type testBadRequestError struct {
122 code int
123 }
124
125 func (err testBadRequestError) Error() string {
126 return "Bad Request"
127 }
128
129 func TestBadRequestError(t *testing.T) {
130 inner := testBadRequestError{1234}
131 var outer error = httptransport.BadRequestError{Err: inner}
132 err := outer.(httptransport.BadRequestError)
133 if want, have := inner, err.Err; want != have {
134 t.Errorf("want %#v, have %#v", want, have)
135 }
136 }