From a6a5e4da22739115508a8f1e605e54f36544062e Mon Sep 17 00:00:00 2001 From: Adrien CABARBAYE Date: Sat, 23 Sep 2023 11:24:14 +0100 Subject: [PATCH 1/3] :sparkles: [`commonerrors`] Marshalling --- utils/commonerrors/errors.go | 72 +++++++++ utils/commonerrors/serialisation.go | 120 ++++++++++++++ utils/commonerrors/serialisation_test.go | 195 +++++++++++++++++++++++ 3 files changed, 387 insertions(+) create mode 100644 utils/commonerrors/serialisation.go create mode 100644 utils/commonerrors/serialisation_test.go diff --git a/utils/commonerrors/errors.go b/utils/commonerrors/errors.go index 92fb4a2d36..768b223ed3 100644 --- a/utils/commonerrors/errors.go +++ b/utils/commonerrors/errors.go @@ -12,6 +12,8 @@ import ( "strings" ) +// List of common errors used to qualify and categorise go errors +// Note: if adding error types to this list, ensure mapping functions (below) are also updated. var ( ErrNotImplemented = errors.New("not implemented") ErrNoExtension = errors.New("missing extension") @@ -85,6 +87,76 @@ func CorrespondTo(target error, description ...string) bool { return false } +// deserialiseCommonError returns the common error corresponding to its string value +func deserialiseCommonError(errStr string) (bool, error) { + errStr = strings.TrimSpace(errStr) + switch { + case errStr == "": + return true, nil + case errStr == ErrInvalid.Error(): + return true, ErrInvalid + case errStr == ErrNotFound.Error(): + return true, ErrNotFound + case CorrespondTo(ErrNotImplemented, errStr): + return true, ErrNotImplemented + case CorrespondTo(ErrNoExtension, errStr): + return true, ErrNoExtension + case CorrespondTo(ErrNoLogger, errStr): + return true, ErrNoLogger + case CorrespondTo(ErrNoLoggerSource, errStr): + return true, ErrNoLoggerSource + case CorrespondTo(ErrNoLogSource, errStr): + return true, ErrNoLogSource + case CorrespondTo(ErrUndefined, errStr): + return true, ErrUndefined + case CorrespondTo(ErrInvalidDestination, errStr): + return true, ErrInvalidDestination + case CorrespondTo(ErrTimeout, errStr): + return true, ErrTimeout + case CorrespondTo(ErrLocked, errStr): + return true, ErrLocked + case CorrespondTo(ErrStaleLock, errStr): + return true, ErrStaleLock + case CorrespondTo(ErrExists, errStr): + return true, ErrExists + case CorrespondTo(ErrNotFound, errStr): + return true, ErrExists + case CorrespondTo(ErrUnsupported, errStr): + return true, ErrUnsupported + case CorrespondTo(ErrUnavailable, errStr): + return true, ErrUnavailable + case CorrespondTo(ErrWrongUser, errStr): + return true, ErrWrongUser + case CorrespondTo(ErrUnauthorised, errStr): + return true, ErrUnauthorised + case CorrespondTo(ErrUnknown, errStr): + return true, ErrUnknown + case CorrespondTo(ErrInvalid, errStr): + return true, ErrInvalid + case CorrespondTo(ErrConflict, errStr): + return true, ErrConflict + case CorrespondTo(ErrMarshalling, errStr): + return true, ErrMarshalling + case CorrespondTo(ErrCancelled, errStr): + return true, ErrCancelled + case CorrespondTo(ErrEmpty, errStr): + return true, ErrEmpty + case CorrespondTo(ErrUnexpected, errStr): + return true, ErrUnexpected + case CorrespondTo(ErrTooLarge, errStr): + return true, ErrTooLarge + case CorrespondTo(ErrForbidden, errStr): + return true, ErrForbidden + case CorrespondTo(ErrCondition, errStr): + return true, ErrCondition + case CorrespondTo(ErrEOF, errStr): + return true, ErrEOF + case CorrespondTo(ErrMalicious, errStr): + return true, ErrMalicious + } + return false, ErrUnknown +} + // ConvertContextError converts a context error into common errors. func ConvertContextError(err error) error { if err == nil { diff --git a/utils/commonerrors/serialisation.go b/utils/commonerrors/serialisation.go new file mode 100644 index 0000000000..156bb4e38f --- /dev/null +++ b/utils/commonerrors/serialisation.go @@ -0,0 +1,120 @@ +package commonerrors + +import ( + "errors" + "fmt" + "strings" +) + +type marshallingError struct { + Reason string + Error error +} + +func (e *marshallingError) MarshalText() (text []byte, err error) { + str := serialiseMarshallingError(e) + return []byte(str), nil +} + +func (e *marshallingError) UnmarshalText(text []byte) error { + er := processErrorStr(string(text)) + if er == nil { + return ErrMarshalling + } + e.Error = er.Error + e.Reason = er.Reason + return nil +} + +func (e *marshallingError) ConvertToError() error { + if e == nil { + return nil + } + if e.Error == nil { + if e.Reason == "" { + return nil + } + return errors.New(e.Reason) + } + if e.Reason == "" { + return e.Error + } + return fmt.Errorf("%w: %v", e.Error, e.Reason) +} + +func processError(err error) (mErr *marshallingError) { + if err == nil { + return + } + mErr = processErrorStr(err.Error()) + if mErr == nil { + mErr = &marshallingError{ + Error: err, + } + return + } + switch x := err.(type) { + case interface{ Unwrap() error }: + mErr.Error = x.Unwrap() + case interface{ Unwrap() []error }: + mErr.Error = errors.Join(x.Unwrap()...) + } + return +} + +func processErrorStr(err string) (mErr *marshallingError) { + err = strings.TrimSpace(err) + if err == "" { + return + } + mErr = &marshallingError{} + elems := strings.Split(err, ":") + found, commonErr := deserialiseCommonError(elems[0]) + if !found || commonErr == nil { + mErr.Error = errors.New(strings.TrimSpace(elems[0])) + } else { + mErr.Error = commonErr + } + if len(elems) > 0 { + var reasonElems []string + for i := 1; i < len(elems); i++ { + reasonElems = append(reasonElems, strings.TrimSpace(elems[i])) + } + mErr.Reason = strings.Join(reasonElems, ": ") + } + return +} + +func serialiseMarshallingError(err *marshallingError) string { + if err == nil { + return "" + } + mErr := err.ConvertToError() + if mErr == nil { + return "" + } + return mErr.Error() +} + +// SerialiseError marshals an error following a certain convention: `error type: reason` +func SerialiseError(err error) ([]byte, error) { + mErr := processError(err) + if mErr == nil { + return nil, nil + } + return mErr.MarshalText() +} + +// DeserialiseError unmarshals text into an error. It tries to determine the error type. +func DeserialiseError(text []byte) (deserialisedError, err error) { + if len(text) == 0 { + return + } + mErr := marshallingError{} + err = mErr.UnmarshalText(text) + if err != nil { + return + } + deserialisedError = mErr.ConvertToError() + return +} diff --git a/utils/commonerrors/serialisation_test.go b/utils/commonerrors/serialisation_test.go new file mode 100644 index 0000000000..359edbfbf7 --- /dev/null +++ b/utils/commonerrors/serialisation_test.go @@ -0,0 +1,195 @@ +package commonerrors + +import ( + "errors" + "fmt" + "github.com/bxcodec/faker/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "strings" + "testing" +) + +func TestDeserialise(t *testing.T) { + sentence := faker.Sentence() + errStr := strings.ToLower(faker.Name()) + var tests = []struct { + text string + expectedReason string + expectedError error + marshallingError bool + }{ + { + text: "", + expectedReason: "", + expectedError: nil, + marshallingError: true, + }, + { + text: errStr, + expectedReason: "", + expectedError: errors.New(errStr), + }, + { + text: sentence, + expectedReason: "", + expectedError: errors.New(sentence), + }, + { + text: fmt.Errorf("%v:%v", errStr, sentence).Error(), + expectedReason: sentence, + expectedError: errors.New(errStr), + }, + { + text: fmt.Errorf("%w: %v", errors.New(errStr), sentence).Error(), + expectedReason: sentence, + expectedError: errors.New(errStr), + }, + { + text: fmt.Errorf("%w : %v", errors.New(errStr), sentence).Error(), + expectedReason: sentence, + expectedError: errors.New(errStr), + }, + { + text: fmt.Errorf("%w : %v", ErrInvalid, sentence).Error(), + expectedReason: sentence, + expectedError: ErrInvalid, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.text, func(t *testing.T) { + mErr := marshallingError{} + if test.marshallingError { + assert.True(t, Any(ErrMarshalling, mErr.UnmarshalText([]byte(test.text)))) + } else { + require.NoError(t, mErr.UnmarshalText([]byte(test.text))) + if test.expectedError == nil { + assert.NoError(t, mErr.Error) + } else { + require.Error(t, mErr.Error) + assert.Equal(t, test.expectedError.Error(), mErr.Error.Error()) + } + + assert.Equal(t, test.expectedReason, mErr.Reason) + } + }) + } +} + +func TestCommonErrorSerialisation(t *testing.T) { + text, err := SerialiseError(nil) + require.NoError(t, err) + assert.Empty(t, text) + commonErr, err := DeserialiseError(nil) + require.NoError(t, err) + assert.NoError(t, commonErr) + commonErr, err = DeserialiseError([]byte{}) + require.NoError(t, err) + assert.NoError(t, commonErr) + commonErr, err = DeserialiseError([]byte("")) + require.NoError(t, err) + assert.NoError(t, commonErr) + tests := []struct { + commonError error + }{ + {commonError: ErrNotImplemented}, + {commonError: ErrNoExtension}, + {commonError: ErrNoLogger}, + {commonError: ErrNoLoggerSource}, + {commonError: ErrNoLogSource}, + {commonError: ErrUndefined}, + {commonError: ErrInvalidDestination}, + {commonError: ErrTimeout}, + {commonError: ErrLocked}, + {commonError: ErrStaleLock}, + {commonError: ErrExists}, + {commonError: ErrNotFound}, + {commonError: ErrUnsupported}, + {commonError: ErrUnavailable}, + {commonError: ErrWrongUser}, + {commonError: ErrUnauthorised}, + {commonError: ErrUnknown}, + {commonError: ErrInvalid}, + {commonError: ErrConflict}, + {commonError: ErrMarshalling}, + {commonError: ErrCancelled}, + {commonError: ErrEmpty}, + {commonError: ErrUnexpected}, + {commonError: ErrTooLarge}, + {commonError: ErrForbidden}, + {commonError: ErrCondition}, + {commonError: ErrEOF}, + {commonError: ErrMalicious}, + } + for i := range tests { + test := tests[i] + t.Run(test.commonError.Error(), func(t *testing.T) { + text, err := SerialiseError(test.commonError) + require.NoError(t, err) + found, dErr := deserialiseCommonError(string(text)) + assert.True(t, found) + assert.True(t, Any(dErr, test.commonError)) + dErr, err = DeserialiseError(text) + require.NoError(t, err) + fmt.Println(dErr) + assert.True(t, Any(dErr, test.commonError)) + }) + } +} + +func TestGenericSerialisation(t *testing.T) { + text, err := SerialiseError(errors.New(faker.Sentence())) + require.NoError(t, err) + assert.NotEmpty(t, text) + deserialisedErr, err := DeserialiseError(text) + require.NoError(t, err) + assert.Error(t, deserialisedErr) + assert.True(t, Any(deserialisedErr, ErrUnknown)) + + tests := []struct { + commonError error + }{ + {commonError: ErrNotImplemented}, + {commonError: ErrNoExtension}, + {commonError: ErrNoLogger}, + {commonError: ErrNoLoggerSource}, + {commonError: ErrNoLogSource}, + {commonError: ErrUndefined}, + {commonError: ErrInvalidDestination}, + {commonError: ErrTimeout}, + {commonError: ErrLocked}, + {commonError: ErrStaleLock}, + {commonError: ErrExists}, + {commonError: ErrNotFound}, + {commonError: ErrUnsupported}, + {commonError: ErrUnavailable}, + {commonError: ErrWrongUser}, + {commonError: ErrUnauthorised}, + {commonError: ErrUnknown}, + {commonError: ErrInvalid}, + {commonError: ErrConflict}, + {commonError: ErrMarshalling}, + {commonError: ErrCancelled}, + {commonError: ErrEmpty}, + {commonError: ErrUnexpected}, + {commonError: ErrTooLarge}, + {commonError: ErrForbidden}, + {commonError: ErrCondition}, + {commonError: ErrEOF}, + {commonError: ErrMalicious}, + } + for i := range tests { + test := tests[i] + t.Run(test.commonError.Error(), func(t *testing.T) { + reason := faker.Sentence() + text, err := SerialiseError(fmt.Errorf("%w: %v", test.commonError, reason)) + require.NoError(t, err) + dErr, err := DeserialiseError(text) + require.NoError(t, err) + assert.True(t, Any(dErr, test.commonError)) + fmt.Println(dErr) + assert.True(t, strings.Contains(dErr.Error(), reason)) + }) + } +} From 91506ef356a145b282a4b2a1f73b1f490415e784 Mon Sep 17 00:00:00 2001 From: Adrien CABARBAYE Date: Tue, 26 Sep 2023 22:24:09 +0200 Subject: [PATCH 2/3] add a way to serialise multiple errors --- changes/20230926155050.feature | 1 + utils/commonerrors/errors.go | 12 ++ utils/commonerrors/serialisation.go | 166 ++++++++++++++++++++--- utils/commonerrors/serialisation_test.go | 69 ++++++++-- 4 files changed, 215 insertions(+), 33 deletions(-) create mode 100644 changes/20230926155050.feature diff --git a/changes/20230926155050.feature b/changes/20230926155050.feature new file mode 100644 index 0000000000..ebafd0b22c --- /dev/null +++ b/changes/20230926155050.feature @@ -0,0 +1 @@ +:sparkles: `[commonerrors]` Add a way to serialise and deserialise errors diff --git a/utils/commonerrors/errors.go b/utils/commonerrors/errors.go index 768b223ed3..a056bf9c62 100644 --- a/utils/commonerrors/errors.go +++ b/utils/commonerrors/errors.go @@ -178,3 +178,15 @@ func Ignore(target error, ignore ...error) error { } return target } + +// IsEmpty states whether an error is empty or not. +// An error is considered empty if it is `nil` or has no description. +func IsEmpty(err error) bool { + if err == nil { + return true + } + if strings.TrimSpace(err.Error()) == "" { + return true + } + return false +} diff --git a/utils/commonerrors/serialisation.go b/utils/commonerrors/serialisation.go index 156bb4e38f..f4bed8e30d 100644 --- a/utils/commonerrors/serialisation.go +++ b/utils/commonerrors/serialisation.go @@ -1,27 +1,50 @@ package commonerrors import ( + "encoding" "errors" "fmt" "strings" ) +const ( + TypeReasonErrorSeparator = ':' + MultipleErrorSeparator = '\n' +) + +type iMarshallingError interface { + encoding.TextMarshaler + encoding.TextUnmarshaler + fmt.Stringer + error + ConvertToError() error + SetWrappedError(err error) +} + type marshallingError struct { - Reason string - Error error + Reason string + ErrorType error } func (e *marshallingError) MarshalText() (text []byte, err error) { - str := serialiseMarshallingError(e) + str := e.String() return []byte(str), nil } +func (e *marshallingError) String() string { + return serialiseMarshallingError(e) +} + +func (e *marshallingError) Error() string { + return e.String() +} + func (e *marshallingError) UnmarshalText(text []byte) error { - er := processErrorStr(string(text)) + er := processErrorStrLine(string(text)) if er == nil { return ErrMarshalling } - e.Error = er.Error + e.ErrorType = er.ErrorType e.Reason = er.Reason return nil } @@ -30,57 +53,154 @@ func (e *marshallingError) ConvertToError() error { if e == nil { return nil } - if e.Error == nil { + if e.ErrorType == nil { if e.Reason == "" { return nil } return errors.New(e.Reason) } if e.Reason == "" { - return e.Error + return e.ErrorType + } + return fmt.Errorf("%w%v %v", e.ErrorType, string(TypeReasonErrorSeparator), e.Reason) +} + +func (e *marshallingError) SetWrappedError(err error) { + e.ErrorType = err +} + +type multiplemarshallingError struct { + subErrs []iMarshallingError +} + +func (m *multiplemarshallingError) MarshalText() (text []byte, err error) { + for i := range m.subErrs { + subtext, suberr := m.subErrs[i].MarshalText() + if suberr != nil { + err = fmt.Errorf("%w%v an error item could not be marshalled%v %v", ErrMarshalling, string(TypeReasonErrorSeparator), string(TypeReasonErrorSeparator), suberr.Error()) + return + } + text = append(text, subtext...) + text = append(text, MultipleErrorSeparator) + } + return +} + +func (m *multiplemarshallingError) String() string { + text, err := m.MarshalText() + if err == nil { + return string(text) + } + return "" +} + +func (m *multiplemarshallingError) Error() string { + return m.String() +} + +func (m *multiplemarshallingError) UnmarshalText(text []byte) error { + sub := processErrorStr(string(text)) + if sub == nil { + return ErrMarshalling + } + if mul, ok := sub.(*multiplemarshallingError); ok { + m.subErrs = mul.subErrs + } else { + m.subErrs = append(m.subErrs, sub) } - return fmt.Errorf("%w: %v", e.Error, e.Reason) + return nil } -func processError(err error) (mErr *marshallingError) { +func (m *multiplemarshallingError) ConvertToError() error { + var errs []error + for i := range m.subErrs { + errs = append(errs, m.subErrs[i].ConvertToError()) + } + return errors.Join(errs...) +} + +func (m *multiplemarshallingError) SetWrappedError(err error) { + if err == nil { + return + } + if x, ok := err.(interface{ Unwrap() []error }); ok { + unwrapped := x.Unwrap() + if len(unwrapped) > len(m.subErrs) { + for i := 0; i < len(unwrapped)-len(m.subErrs); i++ { + m.subErrs = append(m.subErrs, &marshallingError{}) + } + } + for i := range unwrapped { + subErr := m.subErrs[i] + if subErr != nil { + subErr.SetWrappedError(unwrapped[i]) + } + } + } +} +func processErrorStr(s string) iMarshallingError { + if strings.Contains(s, string(MultipleErrorSeparator)) { + elems := strings.Split(s, string(MultipleErrorSeparator)) + m := &multiplemarshallingError{} + for i := range elems { + mErr := processErrorStrLine(elems[i]) + + if mErr != nil { + m.subErrs = append(m.subErrs, mErr) + } + } + return m + } + return processErrorStrLine(s) +} + +func processError(err error) (mErr iMarshallingError) { if err == nil { return } mErr = processErrorStr(err.Error()) - if mErr == nil { + if IsEmpty(mErr) { mErr = &marshallingError{ - Error: err, + ErrorType: fmt.Errorf("%w%v error `%T` with no description returned", ErrUnknown, string(TypeReasonErrorSeparator), err), } return } switch x := err.(type) { case interface{ Unwrap() error }: - mErr.Error = x.Unwrap() + mErr.SetWrappedError(x.Unwrap()) case interface{ Unwrap() []error }: - mErr.Error = errors.Join(x.Unwrap()...) + unwrap := x.Unwrap() + var nonNilUnwrappedErrors []error + for i := range unwrap { + if !IsEmpty(unwrap[i]) { + nonNilUnwrappedErrors = append(nonNilUnwrappedErrors, unwrap[i]) + } + } + mErr.SetWrappedError(errors.Join(nonNilUnwrappedErrors...)) } return } -func processErrorStr(err string) (mErr *marshallingError) { +func processErrorStrLine(err string) (mErr *marshallingError) { err = strings.TrimSpace(err) if err == "" { + mErr = nil return } mErr = &marshallingError{} - elems := strings.Split(err, ":") + elems := strings.Split(err, string(TypeReasonErrorSeparator)) found, commonErr := deserialiseCommonError(elems[0]) if !found || commonErr == nil { - mErr.Error = errors.New(strings.TrimSpace(elems[0])) + mErr.SetWrappedError(errors.New(strings.TrimSpace(elems[0]))) } else { - mErr.Error = commonErr + mErr.SetWrappedError(commonErr) } if len(elems) > 0 { var reasonElems []string for i := 1; i < len(elems); i++ { reasonElems = append(reasonElems, strings.TrimSpace(elems[i])) } - mErr.Reason = strings.Join(reasonElems, ": ") + mErr.Reason = strings.Join(reasonElems, fmt.Sprintf("%v ", string(TypeReasonErrorSeparator))) } return } @@ -96,7 +216,7 @@ func serialiseMarshallingError(err *marshallingError) string { return mErr.Error() } -// SerialiseError marshals an error following a certain convention: `error type: reason` +// SerialiseError marshals an error following a certain convention: `error type: reason`. func SerialiseError(err error) ([]byte, error) { mErr := processError(err) if mErr == nil { @@ -110,7 +230,13 @@ func DeserialiseError(text []byte) (deserialisedError, err error) { if len(text) == 0 { return } - mErr := marshallingError{} + var mErr iMarshallingError + + if strings.Contains(string(text), string(MultipleErrorSeparator)) { + mErr = &multiplemarshallingError{} + } else { + mErr = &marshallingError{} + } err = mErr.UnmarshalText(text) if err != nil { return diff --git a/utils/commonerrors/serialisation_test.go b/utils/commonerrors/serialisation_test.go index 359edbfbf7..231cc9da42 100644 --- a/utils/commonerrors/serialisation_test.go +++ b/utils/commonerrors/serialisation_test.go @@ -3,15 +3,16 @@ package commonerrors import ( "errors" "fmt" + "strings" + "testing" + "github.com/bxcodec/faker/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "strings" - "testing" ) func TestDeserialise(t *testing.T) { - sentence := faker.Sentence() + sentence := strings.ReplaceAll(faker.Sentence(), "\n", ";") errStr := strings.ToLower(faker.Name()) var tests = []struct { text string @@ -65,10 +66,10 @@ func TestDeserialise(t *testing.T) { } else { require.NoError(t, mErr.UnmarshalText([]byte(test.text))) if test.expectedError == nil { - assert.NoError(t, mErr.Error) + assert.NoError(t, mErr.ErrorType) } else { - require.Error(t, mErr.Error) - assert.Equal(t, test.expectedError.Error(), mErr.Error.Error()) + require.Error(t, mErr.ErrorType) + assert.Equal(t, test.expectedError.Error(), mErr.ErrorType.Error()) } assert.Equal(t, test.expectedReason, mErr.Reason) @@ -132,20 +133,63 @@ func TestCommonErrorSerialisation(t *testing.T) { assert.True(t, Any(dErr, test.commonError)) dErr, err = DeserialiseError(text) require.NoError(t, err) - fmt.Println(dErr) assert.True(t, Any(dErr, test.commonError)) }) } } -func TestGenericSerialisation(t *testing.T) { - text, err := SerialiseError(errors.New(faker.Sentence())) +type multiErr []error + +func (m multiErr) Error() string { return errors.Join(m...).Error() } +func (m multiErr) Unwrap() []error { return []error(m) } + +func TestMultipleError(t *testing.T) { + expectedErr := multiErr([]error{fmt.Errorf("%w: %v", ErrInvalid, strings.ReplaceAll(faker.Sentence(), string(MultipleErrorSeparator), "sep")), errors.New(""), fmt.Errorf("%w: %v", ErrUnexpected, strings.ReplaceAll(faker.Sentence(), string(MultipleErrorSeparator), ";"))}) + text, err := SerialiseError(expectedErr) require.NoError(t, err) assert.NotEmpty(t, text) + deserialisedErr, err := DeserialiseError(text) require.NoError(t, err) assert.Error(t, deserialisedErr) - assert.True(t, Any(deserialisedErr, ErrUnknown)) + subErrors := expectedErr.Unwrap() + for i := range subErrors { + assert.True(t, CorrespondTo(deserialisedErr, subErrors[i].Error()), subErrors[i].Error()) + } +} +func TestGenericSerialisation(t *testing.T) { + t.Run("error with no type", func(t *testing.T) { + expectedErr := errors.New(strings.ReplaceAll(faker.Sentence(), "\n", ";")) + text, err := SerialiseError(expectedErr) + require.NoError(t, err) + assert.NotEmpty(t, text) + deserialisedErr, err := DeserialiseError(text) + require.NoError(t, err) + assert.Error(t, deserialisedErr) + assert.Equal(t, expectedErr.Error(), deserialisedErr.Error()) + }) + t.Run("no error", func(t *testing.T) { + text, err := SerialiseError(nil) + require.NoError(t, err) + assert.Empty(t, text) + }) + t.Run("error with no description", func(t *testing.T) { + expectedErr := errors.New(" ") + assert.True(t, IsEmpty(expectedErr)) + text, err := SerialiseError(expectedErr) + require.NoError(t, err) + assert.NotEmpty(t, text) + deserialisedErr, err := DeserialiseError(text) + require.NoError(t, err) + assert.True(t, Any(deserialisedErr, ErrUnknown)) + }) + t.Run("error deserialisation", func(t *testing.T) { + text := []byte(" ") + deserialisedErr, err := DeserialiseError(text) + require.Error(t, err) + assert.True(t, Any(err, ErrMarshalling)) + assert.NoError(t, deserialisedErr) + }) tests := []struct { commonError error @@ -182,13 +226,12 @@ func TestGenericSerialisation(t *testing.T) { for i := range tests { test := tests[i] t.Run(test.commonError.Error(), func(t *testing.T) { - reason := faker.Sentence() - text, err := SerialiseError(fmt.Errorf("%w: %v", test.commonError, reason)) + reason := strings.ReplaceAll(faker.Sentence(), "\n", ";") + text, err := SerialiseError(fmt.Errorf("%w : %v", test.commonError, reason)) require.NoError(t, err) dErr, err := DeserialiseError(text) require.NoError(t, err) assert.True(t, Any(dErr, test.commonError)) - fmt.Println(dErr) assert.True(t, strings.Contains(dErr.Error(), reason)) }) } From 5d2f1221d387198a36ae700de5fa38bab6e1a579 Mon Sep 17 00:00:00 2001 From: Adrien CABARBAYE Date: Wed, 27 Sep 2023 10:46:25 +0200 Subject: [PATCH 3/3] linting --- utils/commonerrors/serialisation.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/commonerrors/serialisation.go b/utils/commonerrors/serialisation.go index f4bed8e30d..bba965d10a 100644 --- a/utils/commonerrors/serialisation.go +++ b/utils/commonerrors/serialisation.go @@ -100,7 +100,7 @@ func (m *multiplemarshallingError) Error() string { func (m *multiplemarshallingError) UnmarshalText(text []byte) error { sub := processErrorStr(string(text)) - if sub == nil { + if IsEmpty(sub) { return ErrMarshalling } if mul, ok := sub.(*multiplemarshallingError); ok { @@ -150,8 +150,9 @@ func processErrorStr(s string) iMarshallingError { } } return m + } else { + return processErrorStrLine(s) } - return processErrorStrLine(s) } func processError(err error) (mErr iMarshallingError) { @@ -184,8 +185,7 @@ func processError(err error) (mErr iMarshallingError) { func processErrorStrLine(err string) (mErr *marshallingError) { err = strings.TrimSpace(err) if err == "" { - mErr = nil - return + return nil } mErr = &marshallingError{} elems := strings.Split(err, string(TypeReasonErrorSeparator))