Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 61 additions & 23 deletions scw/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,29 @@ func unmarshalStandardError(errorType string, body []byte) error {
}

func unmarshalNonStandardError(errorType string, body []byte) error {
var stdErr SdkError
switch errorType {

// Only in instance API.
case "invalid_request_error":
invalidRequestError := &InvalidRequestError{RawBody: body}
err := json.Unmarshal(body, invalidRequestError)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}
stdErr = invalidRequestError.ToInvalidArgumentsError()
}

return stdErr
return invalidRequestError.ToSdkError()

// Only in instance API.
case "conflict":
conflictError := &ConflictError{RawBody: body}
err := json.Unmarshal(body, conflictError)
if err != nil {
return errors.Wrap(err, "could not parse error %s response body", errorType)
}

return conflictError.ToSdkError()
default:
return nil
}
}

type InvalidArgumentsErrorDetail struct {
Expand Down Expand Up @@ -185,34 +195,62 @@ func (e *InvalidArgumentsError) GetRawBody() json.RawMessage {
return e.RawBody
}

// InvalidRequestError are only returned by the compute API
// InvalidRequestError is only returned by the instance API.
// Warning: this is not a standard error.
type InvalidRequestError struct {
Message string `json:"message"`

Fields map[string][]string `json:"fields"`

RawBody json.RawMessage `json:"-"`
}

// ToInvalidArgumentsError converts it to the standard error InvalidArgumentsError
func (e *InvalidRequestError) ToInvalidArgumentsError() *InvalidArgumentsError {
invalidArguments := &InvalidArgumentsError{
RawBody: e.RawBody,
// ToSdkError converts it to the standard error InvalidArgumentsError
func (e *InvalidRequestError) ToSdkError() SdkError {
// If error has fields, it is an invalid arguments error.
if e.Fields != nil {
invalidArguments := &InvalidArgumentsError{
RawBody: e.RawBody,
}
fieldNames := []string(nil)
for fieldName := range e.Fields {
fieldNames = append(fieldNames, fieldName)
}
sort.Strings(fieldNames)
for _, fieldName := range fieldNames {
for _, message := range e.Fields[fieldName] {
invalidArguments.Details = append(invalidArguments.Details, InvalidArgumentsErrorDetail{
ArgumentName: fieldName,
Reason: "constraint",
HelpMessage: message,
})
}
}
return invalidArguments
}
fieldNames := []string(nil)
for fieldName := range e.Fields {
fieldNames = append(fieldNames, fieldName)

return &ResponseError{
Message: strings.ToLower(e.Message),
Type: "invalid_request_error",
RawBody: e.RawBody,
}
sort.Strings(fieldNames)
for _, fieldName := range fieldNames {
for _, message := range e.Fields[fieldName] {
invalidArguments.Details = append(invalidArguments.Details, InvalidArgumentsErrorDetail{
ArgumentName: fieldName,
Reason: "constraint",
HelpMessage: message,
})
}
}

// ConflictError is only returned by the instance API.
// Warning: this is not a standard error.
type ConflictError struct {
Message string `json:"message"`

RawBody json.RawMessage `json:"-"`
}

// ToSdkError converts it to the standard error InvalidArgumentsError
func (e *ConflictError) ToSdkError() SdkError {
return &ResponseError{
Message: strings.ToLower(e.Message),
Type: "conflict",
RawBody: e.RawBody,
}
return invalidArguments
}

type QuotasExceededError struct {
Expand Down
84 changes: 62 additions & 22 deletions scw/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,71 @@ func TestHasResponseErrorWithoutBody(t *testing.T) {
}

func TestNonStandardError(t *testing.T) {
// Create expected error response
testErrorReponse := &InvalidArgumentsError{
Details: []InvalidArgumentsErrorDetail{
{
ArgumentName: "volumes.5.id",
Reason: "constraint",
HelpMessage: "92 is not a valid UUID.",
},
{
ArgumentName: "volumes.5.name",
Reason: "constraint",
HelpMessage: "required key not provided",
},
},
RawBody: []byte(`{"fields":{"volumes.5.id":["92 is not a valid UUID."],"volumes.5.name":["required key not provided"]},"message":"Validation Error","type":"invalid_request_error"}`),

type testCase struct {
resStatus string
resStatusCode int
resBody string
expectedError SdkError
}

// Create response body with marshalled error response
body := `{"fields":{"volumes.5.id":["92 is not a valid UUID."],"volumes.5.name":["required key not provided"]},"message":"Validation Error","type":"invalid_request_error"}`
res := &http.Response{Status: "400 Bad Request", StatusCode: 400, Body: ioutil.NopCloser(strings.NewReader(body))}
run := func(c *testCase) func(t *testing.T) {
return func(t *testing.T) {
res := &http.Response{
Status: c.resStatus,
StatusCode: c.resStatusCode,
Body: ioutil.NopCloser(strings.NewReader(c.resBody)),
}

// Test that hasResponseError converts the response to the expected SdkError.
newErr := hasResponseError(res)
testhelpers.Assert(t, newErr != nil, "Should have error")
testhelpers.Equals(t, c.expectedError, newErr)
}
}

// Test hasResponseError convert the response into a InvalidArgumentsError error
newErr := hasResponseError(res)
testhelpers.Assert(t, newErr != nil, "Should have error")
testhelpers.Equals(t, testErrorReponse, newErr)
t.Run("invalid_request_error type with fields", run(&testCase{
resStatus: "400 Bad Request",
resStatusCode: http.StatusBadRequest,
resBody: `{"fields":{"volumes.5.id":["92 is not a valid UUID."],"volumes.5.name":["required key not provided"]},"message":"Validation Error","type":"invalid_request_error"}`,
expectedError: &InvalidArgumentsError{
Details: []InvalidArgumentsErrorDetail{
{
ArgumentName: "volumes.5.id",
Reason: "constraint",
HelpMessage: "92 is not a valid UUID.",
},
{
ArgumentName: "volumes.5.name",
Reason: "constraint",
HelpMessage: "required key not provided",
},
},
RawBody: []byte(`{"fields":{"volumes.5.id":["92 is not a valid UUID."],"volumes.5.name":["required key not provided"]},"message":"Validation Error","type":"invalid_request_error"}`),
},
}))

t.Run("invalid_request_error type with message", run(&testCase{
resStatus: "400 Bad Request",
resStatusCode: http.StatusBadRequest,
resBody: `{"message": "server should be running", "type": "invalid_request_error"}`,
expectedError: &ResponseError{
Message: "server should be running",
Type: "invalid_request_error",
RawBody: []byte(`{"message": "server should be running", "type": "invalid_request_error"}`),
},
}))

t.Run("conflict type", run(&testCase{
resStatus: "409 Conflict",
resStatusCode: http.StatusConflict,
resBody: `{"message": "Group is in use. You cannot delete it.", "type": "conflict"}`,
expectedError: &ResponseError{
Message: "group is in use. you cannot delete it.",
Type: "conflict",
RawBody: []byte(`{"message": "Group is in use. You cannot delete it.", "type": "conflict"}`),
},
}))
}

func TestHasResponseErrorWithValidError(t *testing.T) {
Expand Down