Skip to content

Commit

Permalink
Added moreInfo to ApplicationError and normalized string representati…
Browse files Browse the repository at this point in the history
…ons of errors
  • Loading branch information
mikouaj committed Nov 4, 2020
1 parent d29b8cb commit 07c4e31
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 32 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## 1.1.0 (November 04, 2020)

ENHANCEMENTS:

* added `AdditionalInfo` property to `ApplicationError`
* string representation of both `Error` and `ApplicationError` was changed

## 1.0.0 (October 01, 2020)

NOTES:

* first version of Equinix rest-go module

FEATURES:

* Resty based client parses Equinix standardized error response body contents
* `GetPaginated` function queries for data on APIs with paginated responses. Pagination
options can be configured by setting up attributes of `PagingConfig`
76 changes: 50 additions & 26 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,29 @@ type ApplicationError struct {
Code string
//Message is textual description of an error
Message string
//Property is a name of resource property that is related to an error
Property string
//AdditionalInfo provides additional information about an error
AdditionalInfo string
}

func (e Error) Error() string {
return fmt.Sprintf("Equinix rest error: httpCode: %v, message: %v", e.HTTPCode, e.Message)
var errorStr = fmt.Sprintf("Equinix REST API error: Message: %q", e.Message)
if e.HTTPCode > 0 {
errorStr = fmt.Sprintf("%s, HTTPCode: %d", errorStr, e.HTTPCode)
}
var appErrorsStr string
for _, appError := range e.ApplicationErrors {
appErrorsStr += "[" + appError.Error() + "] "
}
if len(appErrorsStr) > 0 {
errorStr += ", ApplicationErrors: " + appErrorsStr
}
return errorStr
}

func (e ApplicationError) Error() string {
return fmt.Sprintf("Code: %q, Property: %q, Message: %q, AdditionalInfo: %q", e.Code, e.Property, e.Message, e.AdditionalInfo)
}

//NewClient creates new Equinix REST client with a given HTTP context, URL and http client.
Expand Down Expand Up @@ -69,16 +88,14 @@ func (c *Client) Execute(req *resty.Request, method string, path string) error {
url := c.baseURL + "/" + path
resp, err := req.SetContext(c.ctx).Execute(method, url)
if err != nil {
restErr := Error{Message: fmt.Sprintf("operation failed: %s", err)}
restErr := Error{Message: "HTTP operation failed: " + err.Error()}
if resp != nil {
restErr.HTTPCode = resp.StatusCode()
}
return restErr
}
if resp.IsError() {
err := transformErrorBody(resp.Body())
err.HTTPCode = resp.StatusCode()
return err
return createError(resp)
}
return nil
}
Expand All @@ -87,38 +104,45 @@ func (c *Client) Execute(req *resty.Request, method string, path string) error {
// Unexported package methods
//_______________________________________________________________________

func transformErrorBody(body []byte) Error {
func mapErrorBodyAPIToDomain(body []byte) ([]ApplicationError, bool) {
apiError := api.ErrorResponse{}
if err := json.Unmarshal(body, &apiError); err == nil {
return mapErrorAPIToDomain(apiError)
return mapApplicationErrorsAPIToDomain([]api.ErrorResponse{apiError}), true
}
apiErrors := api.ErrorResponses{}
if err := json.Unmarshal(body, &apiErrors); err == nil {
return mapErrorsAPIToDomain(apiErrors)
return mapApplicationErrorsAPIToDomain(apiErrors), true
}
return Error{
Message: string(body)}
return nil, false
}

func mapErrorAPIToDomain(apiError api.ErrorResponse) Error {
return Error{
Message: apiError.ErrorMessage,
ApplicationErrors: []ApplicationError{{
apiError.ErrorCode,
fmt.Sprintf("[Error: Property: %v, %v]", apiError.Property, apiError.ErrorMessage),
}},
func mapApplicationErrorsAPIToDomain(apiErrors api.ErrorResponses) []ApplicationError {
transformed := make([]ApplicationError, len(apiErrors))
for i := range apiErrors {
transformed[i] = mapApplicationErrorAPIToDomain(apiErrors[i])
}
return transformed
}

func mapErrorsAPIToDomain(apiErrors api.ErrorResponses) Error {
errors := make([]ApplicationError, len(apiErrors))
msg := ""
for i, v := range apiErrors {
errors[i] = ApplicationError{v.ErrorCode, v.ErrorMessage}
msg = msg + fmt.Sprintf(" [Error %v: Property: %v, %v]", i+1, v.Property, v.ErrorMessage)
func mapApplicationErrorAPIToDomain(apiError api.ErrorResponse) ApplicationError {
return ApplicationError{
Code: apiError.ErrorCode,
Property: apiError.Property,
Message: apiError.ErrorMessage,
AdditionalInfo: apiError.MoreInfo,
}
return Error{
Message: "Multiple errors occurred: " + msg,
ApplicationErrors: errors,
}

func createError(resp *resty.Response) Error {
respBody := resp.Body()
err := Error{}
err.HTTPCode = resp.StatusCode()
err.Message = http.StatusText(err.HTTPCode)
appErrors, ok := mapErrorBodyAPIToDomain(respBody)
if !ok {
err.Message = string(respBody)
return err
}
err.ApplicationErrors = appErrors
return err
}
32 changes: 26 additions & 6 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"testing"

"github.com/equinix/rest-go/internal/api"
Expand Down Expand Up @@ -35,14 +36,14 @@ func TestSingleError(t *testing.T) {

//then
assert.NotNil(t, err, "Error should be returned")
assert.Contains(t, err.Error(), fmt.Sprintf("httpCode: %d", respCode), "Error message contains http status code")
assert.IsType(t, Error{}, err, "Error should be rest.Error type")
restErr := err.(Error)
assert.Equal(t, respCode, restErr.HTTPCode, "rest.Error should have valid httpCode")
assert.Equal(t, http.StatusText(respCode), restErr.Message, "rest.Error should have valid Message")
verifyErrorString(t, restErr, respCode, 1)
assert.Equal(t, 1, len(restErr.ApplicationErrors), "rest.Error should have one application error")
appError := restErr.ApplicationErrors[0]
assert.Equal(t, resp.ErrorCode, appError.Code, "Application error code matches")
assert.Contains(t, appError.Message, appError.Message, "Application error message contains response message")
verifyApplicationError(t, resp, restErr.ApplicationErrors[0])
verifyApplicationErrorString(t, restErr.ApplicationErrors[0].Error())
}

func ReadJSONData(filePath string, target interface{}) error {
Expand Down Expand Up @@ -76,10 +77,12 @@ func TestMultipleError(t *testing.T) {
assert.IsType(t, Error{}, err, "Error should be rest.Error type")
restErr := err.(Error)
assert.Equal(t, respCode, restErr.HTTPCode, "rest.Error should have valid httpCode")
assert.Equal(t, http.StatusText(respCode), restErr.Message, "rest.Error should have valid Message")
verifyErrorString(t, restErr, respCode, len(resp))
assert.Equal(t, len(resp), len(restErr.ApplicationErrors), "rest.Error should have valid number of application errors")
for i := range restErr.ApplicationErrors {
assert.Equal(t, resp[i].ErrorCode, restErr.ApplicationErrors[i].Code, "Application error code matches")
assert.Contains(t, restErr.ApplicationErrors[i].Message, resp[i].ErrorMessage, "Application error message contains response message")
verifyApplicationError(t, resp[i], restErr.ApplicationErrors[i])
verifyApplicationErrorString(t, restErr.ApplicationErrors[i].Error())
}
}

Expand All @@ -94,3 +97,20 @@ func SetupMockedClient(method string, url string, respCode int, resp interface{}
)
return testHc
}

func verifyErrorString(t *testing.T, err Error, statusCode int, appErrorsLen int) {
statusTxt := http.StatusText(statusCode)
regexpStr := fmt.Sprintf("Message: \"%s\", HTTPCode: %d, ApplicationErrors: (\\[.+\\] ){%d}", statusTxt, statusCode, appErrorsLen)
assert.Regexp(t, regexp.MustCompile(regexpStr), err.Error(), "Error produces valid error string")
}

func verifyApplicationError(t *testing.T, apiErr api.ErrorResponse, err ApplicationError) {
assert.Equal(t, apiErr.ErrorCode, err.Code, "ErrorCode matches")
assert.Equal(t, apiErr.Property, err.Property, "Property matches")
assert.Equal(t, apiErr.ErrorMessage, err.Message, "ErrorMessage matches")
assert.Equal(t, apiErr.MoreInfo, err.AdditionalInfo, "AdditionalInfo matches")
}

func verifyApplicationErrorString(t *testing.T, appErrorStr string) {
assert.Regexp(t, regexp.MustCompile("^Code: \".*\", Property: \".*\", Message: \".*\", AdditionalInfo: \".*\"$"), appErrorStr, "ApplicationError produces valid error string")
}

0 comments on commit 07c4e31

Please sign in to comment.