-
Notifications
You must be signed in to change notification settings - Fork 18
/
httplib.go
139 lines (132 loc) · 3.61 KB
/
httplib.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package trace
import (
"encoding/json"
"fmt"
"net/http"
)
// WriteError sets up HTTP error response and writes it to writer w
func WriteError(w http.ResponseWriter, err error) {
if !IsAggregate(err) {
replyJSON(w, ErrorToCode(err), err)
return
}
for i := 0; i < maxHops; i++ {
var aggErr Aggregate
var ok bool
if aggErr, ok = Unwrap(err).(Aggregate); !ok {
break
}
errors := aggErr.Errors()
if len(errors) == 0 {
break
}
err = errors[0]
}
replyJSON(w, ErrorToCode(err), err)
}
// ErrorToCode returns an appropriate HTTP status code based on the provided error type
func ErrorToCode(err error) int {
switch {
case IsAggregate(err):
return http.StatusGatewayTimeout
case IsNotFound(err):
return http.StatusNotFound
case IsBadParameter(err) || IsOAuth2(err):
return http.StatusBadRequest
case IsNotImplemented(err):
return http.StatusNotImplemented
case IsCompareFailed(err):
return http.StatusPreconditionFailed
case IsAccessDenied(err):
return http.StatusForbidden
case IsAlreadyExists(err):
return http.StatusConflict
case IsLimitExceeded(err):
return http.StatusTooManyRequests
case IsConnectionProblem(err):
return http.StatusGatewayTimeout
default:
return http.StatusInternalServerError
}
}
// ReadError converts http error to internal error type
// based on HTTP response code and HTTP body contents
// if status code does not indicate error, it will return nil
func ReadError(statusCode int, respBytes []byte) error {
if statusCode >= http.StatusOK && statusCode < http.StatusBadRequest {
return nil
}
var err error
switch statusCode {
case http.StatusNotFound:
err = &NotFoundError{}
case http.StatusBadRequest:
err = &BadParameterError{}
case http.StatusNotImplemented:
err = &NotImplementedError{}
case http.StatusPreconditionFailed:
err = &CompareFailedError{}
case http.StatusForbidden:
err = &AccessDeniedError{}
case http.StatusConflict:
err = &AlreadyExistsError{}
case http.StatusTooManyRequests:
err = &LimitExceededError{}
case http.StatusGatewayTimeout:
err = &ConnectionProblemError{}
default:
err = &RawTrace{}
}
return wrapProxy(unmarshalError(err, respBytes))
}
func replyJSON(w http.ResponseWriter, code int, err error) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
var out []byte
// wrap regular errors in order to achieve unification
// and provide structurally consistent responses
obj, ok := err.(*TraceErr)
if !ok {
obj = &TraceErr{Err: err}
}
out, err = json.MarshalIndent(obj, "", " ")
if err != nil {
out = []byte(fmt.Sprintf(`{"error": {"message": "internal marshal error: %v"}}`, err))
}
w.Write(out)
}
func unmarshalError(err error, responseBody []byte) error {
if len(responseBody) == 0 {
return err
}
var raw RawTrace
if err2 := json.Unmarshal(responseBody, &raw); err2 != nil {
return errorOnInvalidJSON(err, responseBody)
}
if len(raw.Err) != 0 {
if err2 := json.Unmarshal(raw.Err, err); err2 != nil {
return errorOnInvalidJSON(err, responseBody)
}
return &TraceErr{
Traces: raw.Traces,
Err: err,
Message: raw.Message,
Messages: raw.Messages,
Fields: raw.Fields,
}
}
if err2 := json.Unmarshal(responseBody, err); err2 != nil {
return errorOnInvalidJSON(err, responseBody)
}
return err
}
// errorOnInvalidJSON is used to construct a TraceErr with the
// input error as Err and the responseBody as Messages.
// This function is used when the responseBody is not valid
// JSON or it contains an unexpected JSON.
func errorOnInvalidJSON(err error, responseBody []byte) error {
return &TraceErr{
Err: err,
Messages: []string{string(responseBody)},
}
}