forked from cloudfoundry/cli
/
errors.go
147 lines (133 loc) · 4.63 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
package ccv3
import (
"encoding/json"
"net/http"
"code.cloudfoundry.org/cli/api/cloudcontroller"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
)
// errorWrapper is the wrapper that converts responses with 4xx and 5xx status
// codes to an error.
type errorWrapper struct {
connection cloudcontroller.Connection
}
func newErrorWrapper() *errorWrapper {
return new(errorWrapper)
}
// Make creates a connection in the wrapped connection and handles errors
// that it returns.
func (e *errorWrapper) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error {
err := e.connection.Make(request, passedResponse)
if rawHTTPStatusErr, ok := err.(ccerror.RawHTTPStatusError); ok {
if rawHTTPStatusErr.StatusCode >= http.StatusInternalServerError {
return convert500(rawHTTPStatusErr)
}
return convert400(rawHTTPStatusErr)
}
return err
}
// Wrap wraps a Cloud Controller connection in this error handling wrapper.
func (e *errorWrapper) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
e.connection = innerconnection
return e
}
func convert400(rawHTTPStatusErr ccerror.RawHTTPStatusError) error {
firstErr, errorResponse, err := unmarshalFirstV3Error(rawHTTPStatusErr)
if err != nil {
return err
}
switch rawHTTPStatusErr.StatusCode {
case http.StatusUnauthorized: // 401
if firstErr.Title == "CF-InvalidAuthToken" {
return ccerror.InvalidAuthTokenError{Message: firstErr.Detail}
}
return ccerror.UnauthorizedError{Message: firstErr.Detail}
case http.StatusForbidden: // 403
return ccerror.ForbiddenError{Message: firstErr.Detail}
case http.StatusNotFound: // 404
return handleNotFound(firstErr)
case http.StatusUnprocessableEntity: // 422
return handleUnprocessableEntity(firstErr)
case http.StatusServiceUnavailable: // 503
if firstErr.Title == "CF-TaskWorkersUnavailable" {
return ccerror.TaskWorkersUnavailableError{Message: firstErr.Detail}
}
return ccerror.ServiceUnavailableError{Message: firstErr.Detail}
default:
return ccerror.V3UnexpectedResponseError{
ResponseCode: rawHTTPStatusErr.StatusCode,
RequestIDs: rawHTTPStatusErr.RequestIDs,
V3ErrorResponse: errorResponse,
}
}
}
func convert500(rawHTTPStatusErr ccerror.RawHTTPStatusError) error {
switch rawHTTPStatusErr.StatusCode {
case http.StatusServiceUnavailable: // 503
firstErr, _, err := unmarshalFirstV3Error(rawHTTPStatusErr)
if err != nil {
return err
}
if firstErr.Title == "CF-TaskWorkersUnavailable" {
return ccerror.TaskWorkersUnavailableError{Message: firstErr.Detail}
}
return ccerror.ServiceUnavailableError{Message: firstErr.Detail}
default:
return ccerror.V3UnexpectedResponseError{
ResponseCode: rawHTTPStatusErr.StatusCode,
RequestIDs: rawHTTPStatusErr.RequestIDs,
V3ErrorResponse: ccerror.V3ErrorResponse{
Errors: []ccerror.V3Error{{
Detail: string(rawHTTPStatusErr.RawResponse),
}},
},
}
}
}
func handleNotFound(errorResponse ccerror.V3Error) error {
switch errorResponse.Detail {
case "App not found":
return ccerror.ApplicationNotFoundError{}
case "Droplet not found":
return ccerror.DropletNotFoundError{}
case "Instance not found":
return ccerror.InstanceNotFoundError{}
case "Process not found":
return ccerror.ProcessNotFoundError{}
default:
return ccerror.ResourceNotFoundError{Message: errorResponse.Detail}
}
}
func handleUnprocessableEntity(errorResponse ccerror.V3Error) error {
switch errorResponse.Detail {
case "name must be unique in space":
return ccerror.NameNotUniqueInSpaceError{}
case "Buildpack must be an existing admin buildpack or a valid git URI":
return ccerror.InvalidBuildpackError{}
default:
return ccerror.UnprocessableEntityError{Message: errorResponse.Detail}
}
}
func unmarshalFirstV3Error(rawHTTPStatusErr ccerror.RawHTTPStatusError) (ccerror.V3Error, ccerror.V3ErrorResponse, error) {
// Try to unmarshal the raw error into a CC error. If unmarshaling fails,
// return the raw error.
var errorResponse ccerror.V3ErrorResponse
err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse)
// error parsing json
if err != nil {
return ccerror.V3Error{}, errorResponse, ccerror.UnknownHTTPSourceError{
StatusCode: rawHTTPStatusErr.StatusCode,
RawResponse: rawHTTPStatusErr.RawResponse,
}
}
errors := errorResponse.Errors
if len(errors) == 0 {
return ccerror.V3Error{}, errorResponse, ccerror.V3UnexpectedResponseError{
ResponseCode: rawHTTPStatusErr.StatusCode,
V3ErrorResponse: errorResponse,
}
}
// There could be multiple errors in the future but for now we only convert
// the first error.
firstErr := errors[0]
return firstErr, errorResponse, nil
}