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