github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/api/cloudcontroller/ccv3/errors.go (about) 1 package ccv3 2 3 import ( 4 "encoding/json" 5 "net/http" 6 7 "code.cloudfoundry.org/cli/api/cloudcontroller" 8 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 9 ) 10 11 // errorWrapper is the wrapper that converts responses with 4xx and 5xx status 12 // codes to an error. 13 type errorWrapper struct { 14 connection cloudcontroller.Connection 15 } 16 17 func newErrorWrapper() *errorWrapper { 18 return new(errorWrapper) 19 } 20 21 // Make creates a connection in the wrapped connection and handles errors 22 // that it returns. 23 func (e *errorWrapper) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error { 24 err := e.connection.Make(request, passedResponse) 25 26 if rawHTTPStatusErr, ok := err.(ccerror.RawHTTPStatusError); ok { 27 if rawHTTPStatusErr.StatusCode >= http.StatusInternalServerError { 28 return convert500(rawHTTPStatusErr) 29 } 30 return convert400(rawHTTPStatusErr, request) 31 } 32 return err 33 } 34 35 // Wrap wraps a Cloud Controller connection in this error handling wrapper. 36 func (e *errorWrapper) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection { 37 e.connection = innerconnection 38 return e 39 } 40 41 func convert400(rawHTTPStatusErr ccerror.RawHTTPStatusError, request *cloudcontroller.Request) error { 42 firstErr, errorResponse, err := unmarshalFirstV3Error(rawHTTPStatusErr) 43 if err != nil { 44 return err 45 } 46 47 if len(errorResponse.Errors) > 1 { 48 return ccerror.MultiError{Errors: errorResponse.Errors, ResponseCode: rawHTTPStatusErr.StatusCode} 49 } 50 51 switch rawHTTPStatusErr.StatusCode { 52 case http.StatusUnauthorized: // 401 53 if firstErr.Title == "CF-InvalidAuthToken" { 54 return ccerror.InvalidAuthTokenError{Message: firstErr.Detail} 55 } 56 return ccerror.UnauthorizedError{Message: firstErr.Detail} 57 case http.StatusForbidden: // 403 58 return ccerror.ForbiddenError{Message: firstErr.Detail} 59 case http.StatusNotFound: // 404 60 return handleNotFound(firstErr, request) 61 case http.StatusUnprocessableEntity: // 422 62 return handleUnprocessableEntity(firstErr) 63 case http.StatusServiceUnavailable: // 503 64 if firstErr.Title == "CF-TaskWorkersUnavailable" { 65 return ccerror.TaskWorkersUnavailableError{Message: firstErr.Detail} 66 } 67 return ccerror.ServiceUnavailableError{Message: firstErr.Detail} 68 default: 69 return ccerror.V3UnexpectedResponseError{ 70 ResponseCode: rawHTTPStatusErr.StatusCode, 71 RequestIDs: rawHTTPStatusErr.RequestIDs, 72 V3ErrorResponse: errorResponse, 73 } 74 } 75 } 76 77 func convert500(rawHTTPStatusErr ccerror.RawHTTPStatusError) error { 78 switch rawHTTPStatusErr.StatusCode { 79 case http.StatusServiceUnavailable: // 503 80 firstErr, _, err := unmarshalFirstV3Error(rawHTTPStatusErr) 81 if err != nil { 82 return err 83 } 84 if firstErr.Title == "CF-TaskWorkersUnavailable" { 85 return ccerror.TaskWorkersUnavailableError{Message: firstErr.Detail} 86 } 87 return ccerror.ServiceUnavailableError{Message: firstErr.Detail} 88 default: 89 return ccerror.V3UnexpectedResponseError{ 90 ResponseCode: rawHTTPStatusErr.StatusCode, 91 RequestIDs: rawHTTPStatusErr.RequestIDs, 92 V3ErrorResponse: ccerror.V3ErrorResponse{ 93 Errors: []ccerror.V3Error{{ 94 Detail: string(rawHTTPStatusErr.RawResponse), 95 }}, 96 }, 97 } 98 } 99 } 100 101 func handleNotFound(errorResponse ccerror.V3Error, request *cloudcontroller.Request) error { 102 switch errorResponse.Detail { 103 case "App not found": 104 return ccerror.ApplicationNotFoundError{} 105 case "Droplet not found": 106 return ccerror.DropletNotFoundError{} 107 case "Deployment not found": 108 return ccerror.DeploymentNotFoundError{} 109 case "Instance not found": 110 return ccerror.InstanceNotFoundError{} 111 case "Process not found": 112 return ccerror.ProcessNotFoundError{} 113 case "Unknown request": 114 return ccerror.APINotFoundError{URL: request.URL.String()} 115 default: 116 return ccerror.ResourceNotFoundError{Message: errorResponse.Detail} 117 } 118 } 119 120 func handleUnprocessableEntity(errorResponse ccerror.V3Error) error { 121 switch errorResponse.Detail { 122 case "name must be unique in space": 123 return ccerror.NameNotUniqueInSpaceError{} 124 case "Buildpack must be an existing admin buildpack or a valid git URI": 125 return ccerror.InvalidBuildpackError{} 126 default: 127 return ccerror.UnprocessableEntityError{Message: errorResponse.Detail} 128 } 129 } 130 131 func unmarshalFirstV3Error(rawHTTPStatusErr ccerror.RawHTTPStatusError) (ccerror.V3Error, ccerror.V3ErrorResponse, error) { 132 // Try to unmarshal the raw error into a CC error. If unmarshaling fails, 133 // return the raw error. 134 var errorResponse ccerror.V3ErrorResponse 135 err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) 136 if err != nil { 137 return ccerror.V3Error{}, errorResponse, ccerror.UnknownHTTPSourceError{ 138 StatusCode: rawHTTPStatusErr.StatusCode, 139 RawResponse: rawHTTPStatusErr.RawResponse, 140 } 141 } 142 143 errors := errorResponse.Errors 144 if len(errors) == 0 { 145 return ccerror.V3Error{}, errorResponse, ccerror.V3UnexpectedResponseError{ 146 ResponseCode: rawHTTPStatusErr.StatusCode, 147 V3ErrorResponse: errorResponse, 148 } 149 } 150 151 return errors[0], errorResponse, nil 152 }