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