github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/api/cloudcontroller/ccv3/errors.go (about)

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