From ba07d56bb3aef9429ed61d517db8b0b215703d12 Mon Sep 17 00:00:00 2001 From: jerbob92 Date: Tue, 18 Jun 2024 09:17:34 +0200 Subject: [PATCH] Add support for multiple errors (#181) --- error.go | 58 ++++++++++++++++++++++++- error_test.go | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/error.go b/error.go index 1c06f09..bd5d64e 100644 --- a/error.go +++ b/error.go @@ -19,6 +19,62 @@ var ErrorTypeAssert = Error{ Message: "Type assertion failed", } +type ErrorDetail struct { + Multiple bool + Errors map[int]map[string][]string +} + +func (ed *ErrorDetail) DetailsForRow(row int) (map[string][]string, error) { + if !ed.Multiple { + return nil, errors.New("error contains a single error, use Error()") + } + + rowDetail, ok := ed.Errors[row] + if !ok { + return nil, nil + } + + return rowDetail, nil +} + +func (ed *ErrorDetail) Details() (map[string][]string, error) { + if ed.Multiple { + return nil, errors.New("error contains multiple errors, use ErrorForRow()") + } + + return ed.Errors[0], nil +} + +func (ed *ErrorDetail) UnmarshalJSON(data []byte) error { + // First attempt to unmarshal singular. + var singularErr map[string][]string + err := json.Unmarshal(data, &singularErr) + if err == nil { + ed.Errors = map[int]map[string][]string{ + 0: singularErr, + } + return nil + } + + // Then attempt to unmarshal multiple. + var multipleErr map[int]map[string][]string + err = json.Unmarshal(data, &multipleErr) + if err == nil { + ed.Errors = multipleErr + ed.Multiple = true + return nil + } + + return err +} + +func (ed *ErrorDetail) MarshalJSON() ([]byte, error) { + if ed.Multiple { + return json.Marshal(ed.Errors) + } + return json.Marshal(ed.Errors[0]) +} + type Error struct { Err error `json:"-"` @@ -26,7 +82,7 @@ type Error struct { Message string `json:"error"` ErrorCode string `json:"code"` - ErrorDetail map[string][]string `json:"error_details,omitempty"` + ErrorDetail *ErrorDetail `json:"error_details,omitempty"` } func (e Error) Error() string { diff --git a/error_test.go b/error_test.go index c9ec21e..9ab55c3 100644 --- a/error_test.go +++ b/error_test.go @@ -1,6 +1,7 @@ package lago import ( + "encoding/json" "errors" "testing" ) @@ -21,3 +22,117 @@ func TestErrorNoErr(t *testing.T) { } t.Logf("%s", noErr.Error()) } + +func TestErrorDetails(t *testing.T) { + var tests = []struct { + name string + input string + want *Error + }{ + { + name: "Single detail", + input: `{ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": { + "transaction_id": [ + "value_already_exist" + ] + } +}`, + want: &Error{ + HTTPStatusCode: 422, + Message: "Unprocessable Entity", + ErrorCode: "validation_errors", + ErrorDetail: &ErrorDetail{ + Multiple: false, + Errors: map[int]map[string][]string{ + 0: { + "transaction_id": { + "value_already_exist", + }, + }, + }, + }, + }, + }, + { + name: "Multiple details", + input: `{ + "status": 422, + "error": "Unprocessable Entity", + "code": "validation_errors", + "error_details": { + "0": { + "transaction_id": [ + "value_already_exist" + ] + }, + "1": { + "transaction_id": [ + "value_already_exist" + ] + }, + "2": { + "transaction_id": [ + "value_already_exist" + ] + } + } +}`, + want: &Error{ + HTTPStatusCode: 422, + Message: "Unprocessable Entity", + ErrorCode: "validation_errors", + ErrorDetail: &ErrorDetail{ + Multiple: true, + Errors: map[int]map[string][]string{ + 0: { + "transaction_id": { + "value_already_exist", + }, + }, + 1: { + "transaction_id": { + "value_already_exist", + }, + }, + 2: { + "transaction_id": { + "value_already_exist", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errObj := &Error{} + err := json.Unmarshal([]byte(tt.input), errObj) + if err != nil { + t.Errorf("got error %s", err.Error()) + return + } + + expectErr, err := json.Marshal(tt.want) + if err != nil { + t.Errorf("got error %s", err.Error()) + return + } + + gotErr, err := json.Marshal(errObj) + if err != nil { + t.Errorf("got error %s", err.Error()) + return + } + + if string(expectErr) != string(gotErr) { + t.Errorf("got error %s, but expected error %s", string(gotErr), string(expectErr)) + return + } + }) + } +}