transport/http: remove Error type
Peter Bourgon
7 years ago
46 | 46 | code := http.StatusInternalServerError |
47 | 47 | msg := err.Error() |
48 | 48 | |
49 | if e, ok := err.(httptransport.Error); ok { | |
50 | msg = e.Err.Error() | |
51 | switch e.Domain { | |
52 | case httptransport.DomainDecode: | |
53 | code = http.StatusBadRequest | |
54 | ||
55 | case httptransport.DomainDo: | |
56 | switch e.Err { | |
57 | case ErrTwoZeroes, ErrMaxSizeExceeded, ErrIntOverflow: | |
58 | code = http.StatusBadRequest | |
59 | } | |
60 | } | |
49 | switch err { | |
50 | case ErrTwoZeroes, ErrMaxSizeExceeded, ErrIntOverflow: | |
51 | code = http.StatusBadRequest | |
61 | 52 | } |
62 | 53 | |
63 | 54 | w.WriteHeader(code) |
404 | 404 | case ErrAlreadyExists, ErrInconsistentIDs: |
405 | 405 | return http.StatusBadRequest |
406 | 406 | default: |
407 | if e, ok := err.(httptransport.Error); ok { | |
408 | switch e.Domain { | |
409 | case httptransport.DomainDecode: | |
410 | return http.StatusBadRequest | |
411 | case httptransport.DomainDo: | |
412 | return http.StatusServiceUnavailable | |
413 | default: | |
414 | return http.StatusInternalServerError | |
415 | } | |
416 | } | |
417 | 407 | return http.StatusInternalServerError |
418 | 408 | } |
419 | 409 | } |
81 | 81 | |
82 | 82 | req, err := http.NewRequest(c.method, c.tgt.String(), nil) |
83 | 83 | if err != nil { |
84 | return nil, Error{Domain: DomainNewRequest, Err: err} | |
84 | return nil, err | |
85 | 85 | } |
86 | 86 | |
87 | 87 | if err = c.enc(ctx, req, request); err != nil { |
88 | return nil, Error{Domain: DomainEncode, Err: err} | |
88 | return nil, err | |
89 | 89 | } |
90 | 90 | |
91 | 91 | for _, f := range c.before { |
94 | 94 | |
95 | 95 | resp, err := ctxhttp.Do(ctx, c.client, req) |
96 | 96 | if err != nil { |
97 | return nil, Error{Domain: DomainDo, Err: err} | |
97 | return nil, err | |
98 | 98 | } |
99 | 99 | if !c.bufferedStream { |
100 | 100 | defer resp.Body.Close() |
106 | 106 | |
107 | 107 | response, err := c.dec(ctx, resp) |
108 | 108 | if err != nil { |
109 | return nil, Error{Domain: DomainDecode, Err: err} | |
109 | return nil, err | |
110 | 110 | } |
111 | 111 | |
112 | 112 | return response, nil |
0 | package http | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ) | |
5 | ||
6 | const ( | |
7 | // DomainNewRequest is an error during request generation. | |
8 | DomainNewRequest = "NewRequest" | |
9 | ||
10 | // DomainEncode is an error during request or response encoding. | |
11 | DomainEncode = "Encode" | |
12 | ||
13 | // DomainDo is an error during the execution phase of the request. | |
14 | DomainDo = "Do" | |
15 | ||
16 | // DomainDecode is an error during request or response decoding. | |
17 | DomainDecode = "Decode" | |
18 | ) | |
19 | ||
20 | // Error is an error that occurred at some phase within the transport. | |
21 | type Error struct { | |
22 | // Domain is the phase in which the error was generated. | |
23 | Domain string | |
24 | ||
25 | // Err is the concrete error. | |
26 | Err error | |
27 | } | |
28 | ||
29 | // Error implements the error interface. | |
30 | func (e Error) Error() string { | |
31 | return fmt.Sprintf("%s: %s", e.Domain, e.Err) | |
32 | } |
0 | package http_test | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "fmt" | |
5 | "net/http" | |
6 | "net/url" | |
7 | "testing" | |
8 | ||
9 | "golang.org/x/net/context" | |
10 | ||
11 | httptransport "github.com/go-kit/kit/transport/http" | |
12 | ) | |
13 | ||
14 | func TestClientEndpointEncodeError(t *testing.T) { | |
15 | var ( | |
16 | sampleErr = errors.New("Oh no, an error") | |
17 | enc = func(context.Context, *http.Request, interface{}) error { return sampleErr } | |
18 | dec = func(context.Context, *http.Response) (interface{}, error) { return nil, nil } | |
19 | ) | |
20 | ||
21 | u := &url.URL{ | |
22 | Scheme: "https", | |
23 | Host: "localhost", | |
24 | Path: "/does/not/matter", | |
25 | } | |
26 | ||
27 | c := httptransport.NewClient( | |
28 | "GET", | |
29 | u, | |
30 | enc, | |
31 | dec, | |
32 | ) | |
33 | ||
34 | _, err := c.Endpoint()(context.Background(), nil) | |
35 | if err == nil { | |
36 | t.Fatal("err == nil") | |
37 | } | |
38 | ||
39 | e, ok := err.(httptransport.Error) | |
40 | if !ok { | |
41 | t.Fatal("err is not of type github.com/go-kit/kit/transport/http.Error") | |
42 | } | |
43 | ||
44 | if want, have := sampleErr, e.Err; want != have { | |
45 | t.Fatalf("want %v, have %v", want, have) | |
46 | } | |
47 | } | |
48 | ||
49 | func ExampleErrorOutput() { | |
50 | sampleErr := errors.New("oh no, an error") | |
51 | err := httptransport.Error{Domain: httptransport.DomainDo, Err: sampleErr} | |
52 | fmt.Println(err) | |
53 | // Output: | |
54 | // Do: oh no, an error | |
55 | } |
84 | 84 | request, err := s.dec(ctx, r) |
85 | 85 | if err != nil { |
86 | 86 | s.logger.Log("err", err) |
87 | s.errorEncoder(ctx, Error{Domain: DomainDecode, Err: err}, w) | |
87 | s.errorEncoder(ctx, err, w) | |
88 | 88 | return |
89 | 89 | } |
90 | 90 | |
91 | 91 | response, err := s.e(ctx, request) |
92 | 92 | if err != nil { |
93 | 93 | s.logger.Log("err", err) |
94 | s.errorEncoder(ctx, Error{Domain: DomainDo, Err: err}, w) | |
94 | s.errorEncoder(ctx, err, w) | |
95 | 95 | return |
96 | 96 | } |
97 | 97 | |
101 | 101 | |
102 | 102 | if err := s.enc(ctx, w, response); err != nil { |
103 | 103 | s.logger.Log("err", err) |
104 | s.errorEncoder(ctx, Error{Domain: DomainEncode, Err: err}, w) | |
104 | s.errorEncoder(ctx, err, w) | |
105 | 105 | return |
106 | 106 | } |
107 | 107 | } |
108 | 108 | |
109 | 109 | // ErrorEncoder is responsible for encoding an error to the ResponseWriter. |
110 | // | |
111 | // In the server implementation, only kit/transport/http.Error values are ever | |
112 | // passed to this function, so you might be tempted to have this function take | |
113 | // one of those directly. But, users are encouraged to use custom ErrorEncoders | |
114 | // to encode all HTTP errors to their clients, and so may want to pass and check | |
115 | // for their own error types. See the example shipping/handling service. | |
110 | // Users are encouraged to use custom ErrorEncoders to encode HTTP errors to | |
111 | // their clients, and will likely want to pass and check for their own error | |
112 | // types. See the example shipping/handling service. | |
116 | 113 | type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter) |
117 | 114 | |
118 | 115 | func defaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { |
119 | switch e := err.(type) { | |
120 | case Error: | |
121 | switch e.Domain { | |
122 | case DomainDecode: | |
123 | http.Error(w, err.Error(), http.StatusBadRequest) | |
124 | case DomainDo: | |
125 | http.Error(w, err.Error(), http.StatusServiceUnavailable) // too aggressive? | |
126 | default: | |
127 | http.Error(w, err.Error(), http.StatusInternalServerError) | |
128 | } | |
129 | default: | |
130 | http.Error(w, err.Error(), http.StatusInternalServerError) | |
131 | } | |
116 | http.Error(w, err.Error(), http.StatusInternalServerError) | |
132 | 117 | } |
21 | 21 | server := httptest.NewServer(handler) |
22 | 22 | defer server.Close() |
23 | 23 | resp, _ := http.Get(server.URL) |
24 | if want, have := http.StatusBadRequest, resp.StatusCode; want != have { | |
24 | if want, have := http.StatusInternalServerError, resp.StatusCode; want != have { | |
25 | 25 | t.Errorf("want %d, have %d", want, have) |
26 | 26 | } |
27 | 27 | } |
36 | 36 | server := httptest.NewServer(handler) |
37 | 37 | defer server.Close() |
38 | 38 | resp, _ := http.Get(server.URL) |
39 | if want, have := http.StatusServiceUnavailable, resp.StatusCode; want != have { | |
39 | if want, have := http.StatusInternalServerError, resp.StatusCode; want != have { | |
40 | 40 | t.Errorf("want %d, have %d", want, have) |
41 | 41 | } |
42 | 42 | } |
59 | 59 | func TestServerErrorEncoder(t *testing.T) { |
60 | 60 | errTeapot := errors.New("teapot") |
61 | 61 | code := func(err error) int { |
62 | if e, ok := err.(httptransport.Error); ok && e.Err == errTeapot { | |
62 | if err == errTeapot { | |
63 | 63 | return http.StatusTeapot |
64 | 64 | } |
65 | 65 | return http.StatusInternalServerError |