forked from drycc/controller-sdk-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherrors.go
288 lines (252 loc) · 9.61 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package drycc
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
const (
// formatErrUnknown is used to create an dynamic error if no error matches
formatErrUnknown = "Unknown Error (%d): %s"
jsonParsingError = "error decoding json response (%s): %s"
// fieldReqMsg is API error stating a field is required.
fieldReqMsg = "This field may not be blank."
invalidUserMsg = "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters."
failedLoginMsg = "Unable to log in with provided credentials."
invalidAppNameMsg = "App name can only contain a-z (lowercase), 0-9 and hyphens"
invalidNameMsg = "Can only contain a-z (lowercase), 0-9 and hyphens"
invalidCertMsg = "Could not load certificate"
invalidPodMsg = "does not exist in application"
invalidDomainMsg = "Hostname does not look valid."
invalidVersionMsg = "version cannot be below 0"
invalidKeyMsg = "Key contains invalid base64 chars"
duplicateUserMsg = "A user with that username already exists."
invalidEmailMsg = "Enter a valid email address."
invalidTagMsg = "No nodes matched the provided labels"
duplicateIDMsg = "Application with this id already exists."
cancellationFailedMsg = "still has applications assigned. Delete or transfer ownership"
duplicateDomainMsg = "Domain is already in use by another application"
duplicateKeyMsg = "Public Key is already in use"
)
var (
// ErrServerError is returned when the server returns a 500.
ErrServerError = errors.New("Internal Server Error")
// ErrMethodNotAllowed is thrown when using a unsupposrted method.
// This should not come up unless there in an bug in the SDK.
ErrMethodNotAllowed = errors.New("Method Not Allowed")
// ErrInvalidUsername is returned when the user specifies an invalid or missing username.
ErrInvalidUsername = errors.New(invalidUserMsg)
// ErrDuplicateUsername is returned when trying to register a user that already exists.
ErrDuplicateUsername = errors.New(duplicateUserMsg)
// ErrMissingPassword is returned when a password is not sent with the request.
ErrMissingPassword = errors.New("A Password is required")
// ErrLogin is returned when the api cannot login fails with provided username and password
ErrLogin = errors.New(failedLoginMsg)
// ErrUnauthorized is given when the API returns a 401.
ErrUnauthorized = errors.New("Unauthorized: Missing or Invalid Token")
// ErrInvalidAppName is returned when the user specifies an invalid app name.
ErrInvalidAppName = errors.New(invalidAppNameMsg)
// ErrConflict is returned when the API returns a 409.
ErrConflict = errors.New("this action could not be completed due to a conflict")
// ErrForbidden is returned when the API returns a 403.
ErrForbidden = errors.New("you do not have permission to perform this action")
// ErrMissingKey is returned when a key is not sent with the request.
ErrMissingKey = errors.New("a key is required")
// ErrDuplicateKey is returned when adding a key that already exists.
ErrDuplicateKey = errors.New(duplicateKeyMsg)
// ErrInvalidName is returned when a name is invalid or missing.
ErrInvalidName = fmt.Errorf("name %s", strings.ToLower(invalidNameMsg))
// ErrInvalidCertificate is returned when a certififate is missing or invalid
ErrInvalidCertificate = errors.New(invalidCertMsg)
// ErrPodNotFound is returned when a pod type is not Found
ErrPodNotFound = errors.New("Pod not found in application")
// ErrInvalidDomain is returned when a domain is missing or invalid
ErrInvalidDomain = errors.New(invalidDomainMsg)
// ErrDuplicateDomain is returned adding domain that is already in use
ErrDuplicateDomain = errors.New(duplicateDomainMsg)
// ErrInvalidImage is returned when a image is missing or invalid
ErrInvalidImage = errors.New("The given image is invalid")
// ErrInvalidVersion is returned when a version is invalid
ErrInvalidVersion = errors.New("The given version is invalid")
// ErrMissingID is returned when a ID is missing
ErrMissingID = errors.New("An id is required")
// ErrInvalidEmail is returned when a user gives an invalid email.
ErrInvalidEmail = errors.New(invalidEmailMsg)
// ErrTagNotFound is returned when no node can be found that matches the tag
ErrTagNotFound = errors.New(invalidTagMsg)
// ErrDuplicateApp is returned when create an app with an ID that already exists
ErrDuplicateApp = errors.New(duplicateIDMsg)
// ErrCancellationFailed is returned when cancelling a user fails.
ErrCancellationFailed = errors.New("failed to delete user because the user still has applications assigned. Delete or transfer ownership")
)
// ErrUnprocessable is returned when the controller throws a 422.
type ErrUnprocessable struct {
errorMsg string
}
// ErrNotFound is returned when the controller throws a 404.
type ErrNotFound struct {
errorMsg string
}
func (e ErrUnprocessable) Error() string {
return fmt.Sprintf("Unable to process your request: %s", e.errorMsg)
}
func (e ErrNotFound) Error() string {
return e.errorMsg
}
// checkForErrors tries to match up an API error with an predefined error in the SDK.
func checkForErrors(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode < 400 {
return nil
}
defer res.Body.Close()
out, err := io.ReadAll(res.Body)
if err != nil {
return unknownServerError(res.StatusCode, err.Error())
}
switch res.StatusCode {
case 400:
bodyMap := make(map[string]interface{})
if err := json.Unmarshal(out, &bodyMap); err != nil {
return unknownServerError(res.StatusCode, string(out))
}
if scanResponse(bodyMap, "username", []string{fieldReqMsg, invalidUserMsg}, true) {
return ErrInvalidUsername
}
if scanResponse(bodyMap, "username", []string{duplicateUserMsg}, true) {
return ErrDuplicateUsername
}
if scanResponse(bodyMap, "password", []string{fieldReqMsg}, true) {
return ErrMissingPassword
}
if scanResponse(bodyMap, "non_field_errors", []string{failedLoginMsg}, true) {
return ErrLogin
}
if scanResponse(bodyMap, "id", []string{invalidAppNameMsg}, true) {
return ErrInvalidAppName
}
if scanResponse(bodyMap, "id", []string{duplicateIDMsg}, true) {
return ErrDuplicateApp
}
if scanResponse(bodyMap, "key", []string{fieldReqMsg}, true) {
return ErrMissingKey
}
if scanResponse(bodyMap, "key", []string{duplicateKeyMsg}, true) {
return ErrDuplicateKey
}
if scanResponse(bodyMap, "public", []string{fieldReqMsg, invalidKeyMsg}, true) {
return ErrMissingKey
}
if scanResponse(bodyMap, "certificate", []string{fieldReqMsg, invalidCertMsg}, false) {
return ErrInvalidCertificate
}
if scanResponse(bodyMap, "name", []string{fieldReqMsg, invalidNameMsg}, true) {
return ErrInvalidName
}
if scanResponse(bodyMap, "domain", []string{invalidDomainMsg}, true) {
return ErrInvalidDomain
}
if scanResponse(bodyMap, "domain", []string{duplicateDomainMsg}, true) {
return ErrDuplicateDomain
}
if scanResponse(bodyMap, "image", []string{fieldReqMsg}, true) {
return ErrInvalidImage
}
if scanResponse(bodyMap, "id", []string{fieldReqMsg}, true) {
return ErrMissingID
}
if scanResponse(bodyMap, "email", []string{invalidEmailMsg}, true) {
return ErrInvalidEmail
}
if v, ok := bodyMap["detail"].(string); ok {
if strings.Contains(v, invalidPodMsg) {
return ErrPodNotFound
}
if strings.Contains(v, invalidVersionMsg) {
return ErrInvalidVersion
}
if strings.Contains(v, invalidTagMsg) {
return ErrTagNotFound
}
}
return unknownServerError(res.StatusCode, string(out))
case 401:
return ErrUnauthorized
case 403:
return ErrForbidden
case 404:
if string(out) != "" {
return ErrNotFound{string(out)}
}
return ErrNotFound{"Not Found"}
case 405:
return ErrMethodNotAllowed
case 409:
bodyMap := make(map[string]interface{})
if err := json.Unmarshal(out, &bodyMap); err != nil {
return unknownServerError(res.StatusCode, fmt.Sprintf(jsonParsingError, err, string(out)))
}
if v, ok := bodyMap["detail"].(string); ok {
if strings.Contains(v, cancellationFailedMsg) {
return ErrCancellationFailed
}
}
return unknownServerError(res.StatusCode, string(out))
case 422:
bodyMap := make(map[string]interface{})
if err := json.Unmarshal(out, &bodyMap); err != nil {
return unknownServerError(res.StatusCode, fmt.Sprintf(jsonParsingError, err, string(out)))
}
if v, ok := bodyMap["detail"].(string); ok {
return ErrUnprocessable{v}
}
return unknownServerError(res.StatusCode, string(out))
case 500:
return ErrServerError
default:
return unknownServerError(res.StatusCode, string(out))
}
}
func arrayContents(m map[string]interface{}, field string) []string {
if v, ok := m[field]; ok {
if a, ok := v.([]interface{}); ok {
sa := []string{}
for _, i := range a {
if s, ok := i.(string); ok {
sa = append(sa, s)
}
}
return sa
}
}
return []string{}
}
func arrayContains(search string, completeMatch bool, array []string) bool {
for _, element := range array {
if completeMatch {
if element == search {
return true
}
} else {
if strings.Contains(element, search) {
return true
}
}
}
return false
}
func unknownServerError(statusCode int, message string) error {
// newlines set from controller aren't evaluated as controller characters, so they need to be replaced
message = strings.Replace(message, `\n`, "\n", -1)
return fmt.Errorf(formatErrUnknown, statusCode, message)
}
func scanResponse(
body map[string]interface{}, field string, errMsgs []string, completeMatch bool) bool {
for _, msg := range errMsgs {
if arrayContains(msg, completeMatch, arrayContents(body, field)) {
return true
}
}
return false
}