-
Notifications
You must be signed in to change notification settings - Fork 1
/
errors.go
135 lines (115 loc) · 3.67 KB
/
errors.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
package clients
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
cerrors "github.com/contiamo/go-base/v4/pkg/errors"
)
type errorResponse struct {
Errors []respError `json:"errors"`
}
type respError struct {
Key string `json:"key"`
Message string `json:"message"`
}
// APIError describes an error during unsuccessful HTTP request to an API
type APIError struct {
// Status is the HTTP status of the API response
Status int
// Header is the set of response headers
Header http.Header
// Response is the bytes of the response body
Response []byte
}
// Error implements the error interface.
// Builds a complete error message out of all the errors served in the API response.
// This should be used in logging, tracing, debugging, etc.
// For user facing errors use `ValidationErrors` function.
func (e APIError) Error() (message string) {
errs := e.ResponseErrors()
messages := make([]string, 0, len(errs)+1) // plus the status message in the beginning
status := http.StatusText(e.Status)
if status == "" {
status = strconv.Itoa(e.Status)
}
messages = append(messages, status)
for _, err := range errs {
messages = append(messages, err.Error())
}
return strings.Join(messages, "; ")
}
// ValidationErrors returns a map of validation errors in case they were present in response errors.
// Otherwise, returns `nil` if there were no validation errors in the response.
func (e APIError) ValidationErrors() (errs cerrors.ValidationErrors) {
allErrs := e.ResponseErrors()
// it's always not more than one `cerrors.ValidationErrors` on the list
for _, err := range allErrs {
switch typed := err.(type) {
case cerrors.ValidationErrors:
return typed
}
}
return nil
}
// ResponseErrors returns a slice of all errors that were present in the API response.
// All the field errors are folded into a single validation error map.
// All the general errors are mapped to regular errors.
// Returns `nil` if the response contained no errors in the expected JSON format.
// For the 522 status code it handles a special case where all the general errors become validation
// errors for the `connection` field.
func (e APIError) ResponseErrors() (errs []error) {
var response errorResponse
err := json.Unmarshal(e.Response, &response)
if err != nil {
return nil
}
validationErrs := make(cerrors.ValidationErrors, len(response.Errors))
connectivityGeneralErrs := make([]string, 0, len(response.Errors))
for _, respErr := range response.Errors {
if respErr.Key != "" {
validationErrs[respErr.Key] = errors.New(respErr.Message)
continue
}
// special case for connectivity errors that should be turned into validation errors
if e.Status == 522 {
connectivityGeneralErrs = append(connectivityGeneralErrs, respErr.Message)
continue
}
errs = append(errs, errors.New(respErr.Message))
}
if len(connectivityGeneralErrs) > 0 {
if validationErrs["connection"] != nil {
connectivityGeneralErrs = append(connectivityGeneralErrs, validationErrs["connection"].Error())
}
validationErrs["connection"] = errors.New(strings.Join(connectivityGeneralErrs, ". "))
}
if len(validationErrs) > 0 {
errs = append(errs, validationErrs)
}
return errs
}
// GetStatusCode returns the api error code, if it exists.
// When err is nil, it always returns 200.
func GetStatusCode(err error) int {
if err == nil {
return 200
}
apiErr, ok := err.(APIError)
if ok {
return apiErr.Status
}
switch err {
case cerrors.ErrAuthorization:
return http.StatusUnauthorized
case cerrors.ErrPermission:
return http.StatusForbidden
case cerrors.ErrNotFound:
return http.StatusNotFound
case cerrors.ErrNotImplemented:
return http.StatusNotImplemented
default:
return 0
}
}