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
|
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
var obj interface{} = err
if _, ok := err.(*TraceErr); !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 err
}
if len(raw.Traces) != 0 && len(raw.Err) != 0 {
err2 := json.Unmarshal(raw.Err, err)
if err2 != nil {
return err
}
return &TraceErr{
Traces: raw.Traces,
Err: err,
Message: raw.Message,
Messages: raw.Messages,
Fields: raw.Fields,
}
}
json.Unmarshal(responseBody, err)
return err
}
|