github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+incompatible/api/cloudcontroller/ccv3/errors.go (about) 1 package ccv3 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strings" 8 9 "code.cloudfoundry.org/cli/api/cloudcontroller" 10 ) 11 12 // CCErrorResponse represents a generic Cloud Controller V3 error response. 13 type CCErrorResponse struct { 14 Errors []CCError `json:"errors"` 15 } 16 17 // CCError represents a cloud controller error. 18 type CCError struct { 19 Code int `json:"code"` 20 Detail string `json:"detail"` 21 Title string `json:"title"` 22 } 23 24 // UnexpectedResponseError is returned when the client gets an error that has 25 // not been accounted for. 26 type UnexpectedResponseError struct { 27 CCErrorResponse 28 29 ResponseCode int 30 RequestIDs []string 31 } 32 33 func (e UnexpectedResponseError) Error() string { 34 messages := []string{ 35 "Unexpected Response", 36 fmt.Sprintf("Response Code: %d", e.ResponseCode), 37 } 38 39 for _, id := range e.RequestIDs { 40 messages = append(messages, fmt.Sprintf("Request ID: %s", id)) 41 } 42 43 for _, ccError := range e.CCErrorResponse.Errors { 44 messages = append(messages, fmt.Sprintf("Code: %d, Title: %s, Detail: %s", ccError.Code, ccError.Title, ccError.Detail)) 45 } 46 47 return strings.Join(messages, "\n") 48 } 49 50 // TaskWorkersUnavailableError represents the case when no Diego workers are 51 // available. 52 type TaskWorkersUnavailableError struct { 53 Message string 54 } 55 56 func (e TaskWorkersUnavailableError) Error() string { 57 return e.Message 58 } 59 60 // errorWrapper is the wrapper that converts responses with 4xx and 5xx status 61 // codes to an error. 62 type errorWrapper struct { 63 connection cloudcontroller.Connection 64 } 65 66 func newErrorWrapper() *errorWrapper { 67 return new(errorWrapper) 68 } 69 70 // Wrap wraps a Cloud Controller connection in this error handling wrapper. 71 func (e *errorWrapper) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection { 72 e.connection = innerconnection 73 return e 74 } 75 76 // Make creates a connection in the wrapped connection and handles errors 77 // that it returns. 78 func (e *errorWrapper) Make(request *http.Request, passedResponse *cloudcontroller.Response) error { 79 err := e.connection.Make(request, passedResponse) 80 81 if rawHTTPStatusErr, ok := err.(cloudcontroller.RawHTTPStatusError); ok { 82 return convert(rawHTTPStatusErr) 83 } 84 return err 85 } 86 87 func convert(rawHTTPStatusErr cloudcontroller.RawHTTPStatusError) error { 88 // Try to unmarshal the raw error into a CC error. If unmarshaling fails, 89 // return the raw error. 90 var errorResponse CCErrorResponse 91 err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) 92 if err != nil { 93 if rawHTTPStatusErr.StatusCode == http.StatusNotFound { 94 return cloudcontroller.NotFoundError{string(rawHTTPStatusErr.RawResponse)} 95 } 96 return rawHTTPStatusErr 97 } 98 99 errors := errorResponse.Errors 100 if len(errors) == 0 { 101 return UnexpectedResponseError{ 102 ResponseCode: rawHTTPStatusErr.StatusCode, 103 CCErrorResponse: errorResponse, 104 } 105 } 106 107 // There could be multiple errors in the future but for now we only convert 108 // the first error. 109 firstErr := errors[0] 110 111 switch rawHTTPStatusErr.StatusCode { 112 case http.StatusUnauthorized: // 401 113 if firstErr.Title == "CF-InvalidAuthToken" { 114 return cloudcontroller.InvalidAuthTokenError{Message: firstErr.Detail} 115 } 116 return cloudcontroller.UnauthorizedError{Message: firstErr.Detail} 117 case http.StatusForbidden: // 403 118 return cloudcontroller.ForbiddenError{Message: firstErr.Detail} 119 case http.StatusNotFound: // 404 120 return cloudcontroller.ResourceNotFoundError{Message: firstErr.Detail} 121 case http.StatusUnprocessableEntity: // 422 122 return cloudcontroller.UnprocessableEntityError{Message: firstErr.Detail} 123 case http.StatusServiceUnavailable: // 503 124 if firstErr.Title == "CF-TaskWorkersUnavailable" { 125 return TaskWorkersUnavailableError{Message: firstErr.Detail} 126 } 127 return cloudcontroller.ServiceUnavailableError{Message: firstErr.Detail} 128 default: 129 return UnexpectedResponseError{ 130 ResponseCode: rawHTTPStatusErr.StatusCode, 131 RequestIDs: rawHTTPStatusErr.RequestIDs, 132 CCErrorResponse: errorResponse, 133 } 134 } 135 return nil 136 }