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  }