forked from linode/linodego
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
151 lines (124 loc) · 3.79 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package linodego
import (
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"github.com/go-resty/resty/v2"
)
const (
ErrorUnsupported = iota
// ErrorFromString is the Code identifying Errors created by string types
ErrorFromString
// ErrorFromError is the Code identifying Errors created by error types
ErrorFromError
// ErrorFromStringer is the Code identifying Errors created by fmt.Stringer types
ErrorFromStringer
)
// Error wraps the LinodeGo error with the relevant http.Response
type Error struct {
Response *http.Response
Code int
Message string
}
// APIErrorReason is an individual invalid request message returned by the Linode API
type APIErrorReason struct {
Reason string `json:"reason"`
Field string `json:"field"`
}
func (r APIErrorReason) Error() string {
if len(r.Field) == 0 {
return r.Reason
}
return fmt.Sprintf("[%s] %s", r.Field, r.Reason)
}
// APIError is the error-set returned by the Linode API when presented with an invalid request
type APIError struct {
Errors []APIErrorReason `json:"errors"`
}
func coupleAPIErrors(r *resty.Response, err error) (*resty.Response, error) {
if err != nil {
// an error was raised in go code, no need to check the resty Response
return nil, NewError(err)
}
if r.Error() == nil {
// no error in the resty Response
return r, nil
}
// handle the resty Response errors
// Check that response is of the correct content-type before unmarshalling
expectedContentType := r.Request.Header.Get("Accept")
responseContentType := r.Header().Get("Content-Type")
// If the upstream Linode API server being fronted fails to respond to the request,
// the http server will respond with a default "Bad Gateway" page with Content-Type
// "text/html".
if r.StatusCode() == http.StatusBadGateway && responseContentType == "text/html" {
return nil, Error{Code: http.StatusBadGateway, Message: http.StatusText(http.StatusBadGateway)}
}
if responseContentType != expectedContentType {
msg := fmt.Sprintf(
"Unexpected Content-Type: Expected: %v, Received: %v\nResponse body: %s",
expectedContentType,
responseContentType,
string(r.Body()),
)
return nil, Error{Code: r.StatusCode(), Message: msg}
}
apiError, ok := r.Error().(*APIError)
if !ok || (ok && len(apiError.Errors) == 0) {
return r, nil
}
return nil, NewError(r)
}
func (e APIError) Error() string {
x := []string{}
for _, msg := range e.Errors {
x = append(x, msg.Error())
}
return strings.Join(x, "; ")
}
func (err Error) Error() string {
return fmt.Sprintf("[%03d] %s", err.Code, err.Message)
}
func (err Error) StatusCode() int {
return err.Code
}
func (err Error) Is(target error) bool {
if x, ok := target.(interface{ StatusCode() int }); ok || errors.As(target, &x) {
return err.StatusCode() == x.StatusCode()
}
return false
}
// NewError creates a linodego.Error with a Code identifying the source err type,
// - ErrorFromString (1) from a string
// - ErrorFromError (2) for an error
// - ErrorFromStringer (3) for a Stringer
// - HTTP Status Codes (100-600) for a resty.Response object
func NewError(err any) *Error {
if err == nil {
return nil
}
switch e := err.(type) {
case *Error:
return e
case *resty.Response:
apiError, ok := e.Error().(*APIError)
if !ok {
return &Error{Code: ErrorUnsupported, Message: "Unexpected Resty Error Response, no error"}
}
return &Error{
Code: e.RawResponse.StatusCode,
Message: apiError.Error(),
Response: e.RawResponse,
}
case error:
return &Error{Code: ErrorFromError, Message: e.Error()}
case string:
return &Error{Code: ErrorFromString, Message: e}
case fmt.Stringer:
return &Error{Code: ErrorFromStringer, Message: e.String()}
default:
return &Error{Code: ErrorUnsupported, Message: fmt.Sprintf("Unsupported type to linodego.NewError: %s", reflect.TypeOf(e))}
}
}