Skip to content

Commit

Permalink
client: Fix bug where API errors weren't including field and weren't …
Browse files Browse the repository at this point in the history
…getting separated
  • Loading branch information
lafentres committed Nov 13, 2023
1 parent bd9eef5 commit 5f688eb
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 13 deletions.
61 changes: 48 additions & 13 deletions client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,59 @@ type DetailedError struct {
// Title is a human-readable summary that explains the type of the problem.
Title string `json:"title,omitempty"`
// Details is an array of structured objects that give details about the error.
Details []struct {
Code string `json:"code"`
Description string `json:"description"`
Field string `json:"field"`
} `json:"type_detail,omitempty"`
Details []TypeDetail `json:"type_detail,omitempty"`
}

type TypeDetail struct {
Code string `json:"code"`
Description string `json:"description"`
Field string `json:"field"`
}

func (td TypeDetail) String() string {
response := ""
if td.Code != "" {
response += td.Code
// If any of the fields that could come next are present, add a space separator
if td.Field != "" || td.Description != "" {
response += " "
}
}

if td.Field != "" {
response += td.Field
// If any of the fields that could come next are present, add a dash separator
if td.Description != "" {
response += " - "
}
}

if td.Description != "" {
response += td.Description
}

return response
}

func (e DetailedError) detailsToString() string {
var response string

for index, details := range e.Details {
response += details.String()

// If we haven't reached the end of the list of error details, add a newline separator between each error
if index < len(e.Details)-1 {
response += "\n"
}
}

return response
}

// Error returns a pretty-printed representation of the error
func (e DetailedError) Error() string {
if len(e.Details) > 0 {
var response string
for i, d := range e.Details {
response += d.Code + " - " + d.Description
if i > len(e.Details)-1 {
response += ", "
}
}
return response
return e.detailsToString()
}

return e.Message
Expand Down
147 changes: 147 additions & 0 deletions client/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,151 @@ func TestClient_ParseDetailedError(t *testing.T) {
assert.Equal(t, de.Title, "The requested resource cannot be found.")
assert.Equal(t, de.Message, "Dataset not found")
})

t.Run("Creating a dataset without a name should return a validation error", func(t *testing.T) {
createDatasetRequest := &Dataset{}
_, err := c.Datasets.Create(ctx, createDatasetRequest)
require.Error(t, err)
assert.ErrorAs(t, err, &de)
assert.Equal(t, http.StatusUnprocessableEntity, de.Status)
assert.Equal(t, "https://api.honeycomb.io/problems/validation-failed", de.Type)
assert.Equal(t, "The provided input is invalid.", de.Title)
assert.Equal(t, "The provided input is invalid.", de.Message)
assert.Equal(t, 1, len(de.Details))
assert.Equal(t, "missing", de.Details[0].Code)
assert.Equal(t, "name", de.Details[0].Field)
assert.Equal(t, "cannot be blank", de.Details[0].Description)
assert.Equal(t, "missing name - cannot be blank", de.detailsToString())
})
}

func TestErrors_DetailedError_detailsToString(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
input DetailedError
expectedOutput string
}{
{
name: "multiple details get separated by newline",
input: DetailedError{
Details: []TypeDetail{
{
Code: "test code1",
Field: "test_field1",
Description: "test description1",
},
{
Code: "test code2",
Field: "test_field2",
Description: "test description2",
},
},
},
expectedOutput: "test code1 test_field1 - test description1\ntest code2 test_field2 - test description2",
},
{
name: "empty details returns empty string",
input: DetailedError{
Details: []TypeDetail{},
},
expectedOutput: "",
},
{
name: "one item in details has no newlines",
input: DetailedError{
Details: []TypeDetail{
{
Code: "test code",
Field: "test_field",
Description: "test description",
},
},
},
expectedOutput: "test code test_field - test description",
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actualOutput := testCase.input.detailsToString()
assert.Equal(t, testCase.expectedOutput, actualOutput)
})
}
}

func TestErrors_TypeDetail_String(t *testing.T) {
testCases := []struct {
name string
input TypeDetail
expectedOutput string
}{
{
name: "happy path: Code, Field, and Description present",
input: TypeDetail{
Code: "test code",
Field: "test_field",
Description: "test description",
},
expectedOutput: "test code test_field - test description",
},
{
name: "all fields blank returns empty string",
input: TypeDetail{},
expectedOutput: "",
},
{
name: "empty Code",
input: TypeDetail{
Field: "test_field",
Description: "test description",
},
expectedOutput: "test_field - test description",
},
{
name: "empty Code and Field",
input: TypeDetail{
Description: "test description",
},
expectedOutput: "test description",
},
{
name: "empty Code and Description",
input: TypeDetail{
Field: "test_field",
},
expectedOutput: "test_field",
},
{
name: "empty Field",
input: TypeDetail{
Code: "test code",
Description: "test description",
},
expectedOutput: "test code test description",
},
{
name: "empty Field and Description",
input: TypeDetail{
Code: "test code",
},
expectedOutput: "test code",
},
{
name: "empty Description",
input: TypeDetail{
Code: "test code",
Field: "test_field",
},
expectedOutput: "test code test_field",
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actualOutput := testCase.input.String()
assert.Equal(t, testCase.expectedOutput, actualOutput)
})
}
}

0 comments on commit 5f688eb

Please sign in to comment.