Skip to content

Commit

Permalink
wrap API response errors (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
0x4c6565 committed Oct 11, 2022
1 parent c5c13ee commit 7b3e3bc
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 68 deletions.
64 changes: 35 additions & 29 deletions pkg/connection/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package connection

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -12,7 +11,7 @@ import (
)

type ResponseBody interface {
ErrorString() string
Error() error
Pagination() APIResponseMetadataPagination
}

Expand All @@ -23,19 +22,43 @@ type APIResponse struct {

// APIResponseBody represents the base API response body
type APIResponseBody struct {
APIResponseBodyError

Metadata APIResponseMetadata `json:"meta"`
Errors []APIResponseError `json:"errors"`
Message string `json:"message"`
}

// APIResponseError represents an API response error
type APIResponseError struct {
type APIResponseBodyError struct {
Errors []APIResponseBodyErrorItem `json:"errors"`
Message string `json:"message"`
}

func (e *APIResponseBodyError) Error() string {
var errArr []string

// Message will be populated in certain circumstances, populate error array if exists
if e.Message != "" {
errArr = append(errArr, fmt.Sprintf("message=\"%s\"", e.Message))
}

for _, err := range e.Errors {
errArr = append(errArr, err.Error())
}

return strings.Join(errArr, "; ")
}

// APIResponseBodyErrorItem represents an API response error
type APIResponseBodyErrorItem struct {
Title string `json:"title"`
Detail string `json:"detail"`
Status int `json:"status"`
Source string `json:"source"`
}

func (a *APIResponseBodyErrorItem) Error() string {
return fmt.Sprintf("title=\"%s\", detail=\"%s\", status=\"%d\", source=\"%s\"", a.Title, a.Detail, a.Status, a.Source)
}

// APIResponseMetadata represents the API response metadata
type APIResponseMetadata struct {
Pagination APIResponseMetadataPagination `json:"pagination"`
Expand Down Expand Up @@ -90,7 +113,7 @@ func (r *APIResponse) ValidateStatusCode(codes []int, respBody ResponseBody) err
}
}

return fmt.Errorf("unexpected status code (%d): %s", r.StatusCode, respBody.ErrorString())
return fmt.Errorf("unexpected status code (%d): %w", r.StatusCode, respBody.Error())
}

type ResponseHandler func(resp *APIResponse) error
Expand Down Expand Up @@ -129,29 +152,12 @@ func (r *APIResponse) HandleResponse(respBody ResponseBody, handlers ...Response
return r.ValidateStatusCode([]int{}, respBody)
}

func (a *APIResponseError) String() string {
return fmt.Sprintf("title=\"%s\", detail=\"%s\", status=\"%d\", source=\"%s\"", a.Title, a.Detail, a.Status, a.Source)
}

func (a *APIResponseError) Error() error {
return errors.New(a.String())
}

// ErrorString returns a formatted error string for API response
func (a *APIResponseBody) ErrorString() string {
var errArr []string

// Message will be populated in certain circumstances, populate error array if exists
if a.Message != "" {
errArr = append(errArr, fmt.Sprintf("message=\"%s\"", a.Message))
// Error returns an error struct with embedded errors from body
func (a *APIResponseBody) Error() error {
return &APIResponseBodyError{
Message: a.Message,
Errors: a.Errors,
}

// Now loop through errors and add to error array
for _, err := range a.Errors {
errArr = append(errArr, err.String())
}

return strings.Join(errArr, "; ")
}

// TotalPages returns amount of pages for API response
Expand Down
78 changes: 39 additions & 39 deletions pkg/connection/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,84 +204,84 @@ func TestAPIResponse_HandleResponse(t *testing.T) {
}

func TestAPIResponseError_String(t *testing.T) {
apiError := APIResponseError{
apiError := APIResponseBodyErrorItem{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
}
expected := "title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\""

t.Run("String_Expected", func(t *testing.T) {
s := apiError.String()

assert.Equal(t, expected, s)
})

t.Run("Error_Expected", func(t *testing.T) {
s := apiError.Error()

assert.Equal(t, expected, s.Error())
assert.Equal(t, expected, s)
})
}

func TestAPIResponseBody_ErrorString(t *testing.T) {
t.Run("SingleError", func(t *testing.T) {
b := APIResponseBody{
Errors: []APIResponseError{
APIResponseError{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
APIResponseBodyError: APIResponseBodyError{
Errors: []APIResponseBodyErrorItem{
APIResponseBodyErrorItem{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
},
},
},
}

msg := b.ErrorString()
err := b.Error()

assert.Equal(t, "title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\"", msg)
assert.Equal(t, "title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\"", err.Error())
})

t.Run("MultipleErrors", func(t *testing.T) {
b := APIResponseBody{
Errors: []APIResponseError{
APIResponseError{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
},
APIResponseError{
Detail: "test detail 2",
Source: "test source 2",
Status: 501,
Title: "test title 2",
APIResponseBodyError: APIResponseBodyError{
Errors: []APIResponseBodyErrorItem{
APIResponseBodyErrorItem{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
},
APIResponseBodyErrorItem{
Detail: "test detail 2",
Source: "test source 2",
Status: 501,
Title: "test title 2",
},
},
},
}

msg := b.ErrorString()
err := b.Error()

assert.Equal(t, "title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\"; title=\"test title 2\", detail=\"test detail 2\", status=\"501\", source=\"test source 2\"", msg)
assert.Equal(t, "title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\"; title=\"test title 2\", detail=\"test detail 2\", status=\"501\", source=\"test source 2\"", err.Error())
})

t.Run("WithMessage", func(t *testing.T) {
b := APIResponseBody{
Errors: []APIResponseError{
APIResponseError{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
APIResponseBodyError: APIResponseBodyError{
Errors: []APIResponseBodyErrorItem{
APIResponseBodyErrorItem{
Detail: "test detail 1",
Source: "test source 1",
Status: 500,
Title: "test title 1",
},
},
Message: "test message 1",
},
Message: "test message 1",
}

msg := b.ErrorString()
err := b.Error()

assert.Equal(t, "message=\"test message 1\"; title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\"", msg)
assert.Equal(t, "message=\"test message 1\"; title=\"test title 1\", detail=\"test detail 1\", status=\"500\", source=\"test source 1\"", err.Error())
})
}

Expand Down

0 comments on commit 7b3e3bc

Please sign in to comment.