github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+incompatible/api/cloudcontroller/ccv3/errors.go (about)

     1  package ccv3
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    10  )
    11  
    12  // CCErrorResponse represents a generic Cloud Controller V3 error response.
    13  type CCErrorResponse struct {
    14  	Errors []CCError `json:"errors"`
    15  }
    16  
    17  // CCError represents a cloud controller error.
    18  type CCError struct {
    19  	Code   int    `json:"code"`
    20  	Detail string `json:"detail"`
    21  	Title  string `json:"title"`
    22  }
    23  
    24  // UnexpectedResponseError is returned when the client gets an error that has
    25  // not been accounted for.
    26  type UnexpectedResponseError struct {
    27  	CCErrorResponse
    28  
    29  	ResponseCode int
    30  	RequestIDs   []string
    31  }
    32  
    33  func (e UnexpectedResponseError) Error() string {
    34  	messages := []string{
    35  		"Unexpected Response",
    36  		fmt.Sprintf("Response Code: %d", e.ResponseCode),
    37  	}
    38  
    39  	for _, id := range e.RequestIDs {
    40  		messages = append(messages, fmt.Sprintf("Request ID:    %s", id))
    41  	}
    42  
    43  	for _, ccError := range e.CCErrorResponse.Errors {
    44  		messages = append(messages, fmt.Sprintf("Code: %d, Title: %s, Detail: %s", ccError.Code, ccError.Title, ccError.Detail))
    45  	}
    46  
    47  	return strings.Join(messages, "\n")
    48  }
    49  
    50  // TaskWorkersUnavailableError represents the case when no Diego workers are
    51  // available.
    52  type TaskWorkersUnavailableError struct {
    53  	Message string
    54  }
    55  
    56  func (e TaskWorkersUnavailableError) Error() string {
    57  	return e.Message
    58  }
    59  
    60  // errorWrapper is the wrapper that converts responses with 4xx and 5xx status
    61  // codes to an error.
    62  type errorWrapper struct {
    63  	connection cloudcontroller.Connection
    64  }
    65  
    66  func newErrorWrapper() *errorWrapper {
    67  	return new(errorWrapper)
    68  }
    69  
    70  // Wrap wraps a Cloud Controller connection in this error handling wrapper.
    71  func (e *errorWrapper) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
    72  	e.connection = innerconnection
    73  	return e
    74  }
    75  
    76  // Make creates a connection in the wrapped connection and handles errors
    77  // that it returns.
    78  func (e *errorWrapper) Make(request *http.Request, passedResponse *cloudcontroller.Response) error {
    79  	err := e.connection.Make(request, passedResponse)
    80  
    81  	if rawHTTPStatusErr, ok := err.(cloudcontroller.RawHTTPStatusError); ok {
    82  		return convert(rawHTTPStatusErr)
    83  	}
    84  	return err
    85  }
    86  
    87  func convert(rawHTTPStatusErr cloudcontroller.RawHTTPStatusError) error {
    88  	// Try to unmarshal the raw error into a CC error. If unmarshaling fails,
    89  	// return the raw error.
    90  	var errorResponse CCErrorResponse
    91  	err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse)
    92  	if err != nil {
    93  		if rawHTTPStatusErr.StatusCode == http.StatusNotFound {
    94  			return cloudcontroller.NotFoundError{string(rawHTTPStatusErr.RawResponse)}
    95  		}
    96  		return rawHTTPStatusErr
    97  	}
    98  
    99  	errors := errorResponse.Errors
   100  	if len(errors) == 0 {
   101  		return UnexpectedResponseError{
   102  			ResponseCode:    rawHTTPStatusErr.StatusCode,
   103  			CCErrorResponse: errorResponse,
   104  		}
   105  	}
   106  
   107  	// There could be multiple errors in the future but for now we only convert
   108  	// the first error.
   109  	firstErr := errors[0]
   110  
   111  	switch rawHTTPStatusErr.StatusCode {
   112  	case http.StatusUnauthorized: // 401
   113  		if firstErr.Title == "CF-InvalidAuthToken" {
   114  			return cloudcontroller.InvalidAuthTokenError{Message: firstErr.Detail}
   115  		}
   116  		return cloudcontroller.UnauthorizedError{Message: firstErr.Detail}
   117  	case http.StatusForbidden: // 403
   118  		return cloudcontroller.ForbiddenError{Message: firstErr.Detail}
   119  	case http.StatusNotFound: // 404
   120  		return cloudcontroller.ResourceNotFoundError{Message: firstErr.Detail}
   121  	case http.StatusUnprocessableEntity: // 422
   122  		return cloudcontroller.UnprocessableEntityError{Message: firstErr.Detail}
   123  	case http.StatusServiceUnavailable: // 503
   124  		if firstErr.Title == "CF-TaskWorkersUnavailable" {
   125  			return TaskWorkersUnavailableError{Message: firstErr.Detail}
   126  		}
   127  		return cloudcontroller.ServiceUnavailableError{Message: firstErr.Detail}
   128  	default:
   129  		return UnexpectedResponseError{
   130  			ResponseCode:    rawHTTPStatusErr.StatusCode,
   131  			RequestIDs:      rawHTTPStatusErr.RequestIDs,
   132  			CCErrorResponse: errorResponse,
   133  		}
   134  	}
   135  	return nil
   136  }