package error import ( "context" "errors" "fmt" "member/internal/lib/error/code" "reflect" "strconv" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/zeromicro/go-zero/core/logx" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func TestFromGRPCError_GivenStatusWithCodeAndMessage_ShouldReturnErr(t *testing.T) { // setup s := status.Error(codes.Code(102399), "FAKE ERROR") // act e := FromGRPCError(s) // assert assert.Equal(t, uint32(10), e.Scope()) assert.Equal(t, uint32(2300), e.Category()) assert.Equal(t, uint32(2399), e.Code()) assert.Equal(t, "FAKE ERROR", e.Error()) } func TestFromGRPCError_GivenNilError_ShouldReturnErr_Scope0_Cat0_Detail0(t *testing.T) { // setup var nilError error = nil // act e := FromGRPCError(nilError) // assert assert.Equal(t, uint32(0), e.Scope()) assert.Equal(t, uint32(0), e.Category()) assert.Equal(t, uint32(0), e.Code()) assert.Equal(t, "", e.Error()) } func TestFromGRPCError_GivenGRPCNativeError_ShouldReturnErr_Scope0_CatGRPC_DetailGRPCUnavailable(t *testing.T) { // setup msg := "GRPC Unavailable ERROR" s := status.Error(codes.Code(codes.Unavailable), msg) // act e := FromGRPCError(s) // assert assert.Equal(t, code.Unset, e.Scope()) assert.Equal(t, code.CatGRPC, e.Category()) assert.Equal(t, uint32(codes.Unavailable), e.Code()) assert.Equal(t, msg, e.Error()) } func TestFromGRPCError_GivenGeneralError_ShouldReturnErr_Scope0_CatGRPC_DetailGRPCUnknown(t *testing.T) { // setup generalErr := errors.New("general error") // act e := FromGRPCError(generalErr) // assert assert.Equal(t, code.Unset, e.Scope()) assert.Equal(t, code.CatGRPC, e.Category()) assert.Equal(t, uint32(codes.Unknown), e.Code()) } func TestToGRPCError_GivenErr_StatusShouldHave_Code112233(t *testing.T) { // setup e := Err{scope: 11, code: 2233, msg: "FAKE MSG"} // act err := ToGRPCError(&e) s, _ := status.FromError(err) // assert assert.Equal(t, 112233, int(s.Code())) assert.Equal(t, "FAKE MSG", s.Message()) } func TestInvalidFormat_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InvalidFormat("field A", "Error description") // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.InvalidFormat, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Equal(t, e.Error(), "invalid format: field A Error description") } func TestInvalidFormatL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() ctx := context.Background() // act e := InvalidFormatL(logx.WithContext(ctx), "field A", "Error description") // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.InvalidFormat, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInvalidRange_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InvalidRange("field A", "Error description") // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.InvalidRange, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Equal(t, e.Error(), "invalid range: field A Error description") } func TestInvalidRangeL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() ctx := context.Background() // act e := InvalidRangeL(logx.WithContext(ctx), "field A", "Error description") // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.InvalidRange, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestNotValidImplementation_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := NotValidImplementation("field A", "Error description") // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.NotValidImplementation, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Equal(t, e.Error(), "not valid implementation: field A Error description") } func TestNotValidImplementationL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := NotValidImplementationL(l, "field A", "Error description") // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.NotValidImplementation, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestDBError_WithStrings_ShouldHasCatDBAndDetailCodeDBError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := DBError("field A", "Error description") // assert assert.Equal(t, code.CatDB, e.Category()) assert.Equal(t, code.DBError, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestDBDataConvert_WithStrings_ShouldHasCatDBAndDetailCodeDBDataConvert(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := DBDataConvert("field A", "Error description") // assert assert.Equal(t, code.CatDB, e.Category()) assert.Equal(t, code.DBDataConvert, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceNotFound_WithStrings_ShouldHasCatResource_DetailCodeResourceNotFound(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := ResourceNotFound("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceNotFound, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInvalidResourceFormat_WithStrings_ShouldHasCatResource_DetailCodeInvalidResourceFormat(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InvalidResourceFormat("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InvalidResourceFormat, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInvalidResourceState_OK(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InvalidResourceState("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InvalidResourceState, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.EqualError(t, e, "invalid resource state: field A Error description") } func TestInvalidResourceStateL_LogError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := InvalidResourceStateL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InvalidResourceState, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.EqualError(t, e, "invalid resource state: field A Error description") } func TestAuthExpired_OK(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := AuthExpired("field A", "Error description") // assert assert.Equal(t, code.CatAuth, e.Category()) assert.Equal(t, code.AuthExpired, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestUnauthorized_WithStrings_ShouldHasCatAuth_DetailCodeUnauthorized(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := Unauthorized("field A", "Error description") // assert assert.Equal(t, code.CatAuth, e.Category()) assert.Equal(t, code.Unauthorized, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInvalidPosixTime_WithStrings_ShouldHasCatAuth_DetailCodeInvalidPosixTime(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InvalidPosixTime("field A", "Error description") // assert assert.Equal(t, code.CatAuth, e.Category()) assert.Equal(t, code.InvalidPosixTime, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestSigAndPayloadNotMatched_WithStrings_ShouldHasCatAuth_DetailCodeSigAndPayloadNotMatched(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := SigAndPayloadNotMatched("field A", "Error description") // assert assert.Equal(t, code.CatAuth, e.Category()) assert.Equal(t, code.SigAndPayloadNotMatched, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestForbidden_WithStrings_ShouldHasCatAuth_DetailCodeForbidden(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := Forbidden("field A", "Error description") // assert assert.Equal(t, code.CatAuth, e.Category()) assert.Equal(t, code.Forbidden, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestXBCInternal_WithStrings_ShouldHasCatResource_DetailCodeXBCInternal(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := XBCInternal("field A", "Error description") // assert assert.Equal(t, code.CatArk, e.Category()) assert.Equal(t, code.ArkInternal, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestGeneralInternalError_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := SystemInternalError("field A", "Error description") // assert assert.Equal(t, code.CatSystem, e.Category()) assert.Equal(t, code.SystemInternalError, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestGeneralInternalErrorL_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := SystemInternalErrorL(l, "field A", "Error description") // assert assert.Equal(t, code.CatSystem, e.Category()) assert.Equal(t, code.SystemInternalError, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestSystemMaintainError_WithStrings_DetailSystemMaintainError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := SystemMaintainErrorL(l, "field A", "Error description") // assert assert.Equal(t, code.CatSystem, e.Category()) assert.Equal(t, code.SystemMaintainError, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceAlreadyExist_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := ResourceAlreadyExist("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceAlreadyExist, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceAlreadyExistL_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := ResourceAlreadyExistL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceAlreadyExist, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceInsufficient_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := ResourceInsufficient("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceInsufficient, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceInsufficientL_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := ResourceInsufficientL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceInsufficient, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInsufficientPermission_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InsufficientPermission("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InsufficientPermission, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInsufficientPermissionL_WithStrings_DetailInternalError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := InsufficientPermissionL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InsufficientPermission, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInvalidMeasurementID_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InvalidMeasurementID("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InvalidMeasurementID, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInvalidMeasurementIDL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := InvalidMeasurementIDL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InvalidMeasurementID, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceExpired_OK(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := ResourceExpired("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceExpired, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceExpiredL_LogError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := ResourceExpiredL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceExpired, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceMigrated_OK(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := ResourceMigrated("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceMigrated, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestResourceMigratedL_LogError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := ResourceMigratedL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.ResourceMigrated, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInsufficientQuota_OK(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := InsufficientQuota("field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InsufficientQuota, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestInsufficientQuotaL_LogError(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := InsufficientQuotaL(l, "field A", "Error description") // assert assert.Equal(t, code.CatResource, e.Category()) assert.Equal(t, code.InsufficientQuota, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestPublish_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := Publish("field A", "Error description") // assert assert.Equal(t, code.CatPubSub, e.Category()) assert.Equal(t, code.Publish, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestPublishL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := PublishL(l, "field A", "Error description") // assert assert.Equal(t, code.CatPubSub, e.Category()) assert.Equal(t, code.Publish, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") } func TestMsgSizeTooLarge_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // act e := MsgSizeTooLarge("Error description") // assert assert.Equal(t, code.CatPubSub, e.Category()) assert.Equal(t, code.MsgSizeTooLarge, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "kafka error: Error description") } func TestMsgSizeTooLargeL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() ctrl := gomock.NewController(t) defer ctrl.Finish() l := logx.WithContext(context.Background()) // act e := MsgSizeTooLargeL(l, "Error description") // assert assert.Equal(t, code.CatPubSub, e.Category()) assert.Equal(t, code.MsgSizeTooLarge, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "kafka error: Error description") } func TestStructErr_WithInternalErr_ShouldIsFuncReportCorrectly(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // arrange 2 layers err layer1Err := fmt.Errorf("layer 1 error") layer2Err := fmt.Errorf("layer 2: %w", layer1Err) // act with error chain: InvalidFormat -> layer 2 err -> layer 1 err e := InvalidFormat("field A", "Error description") e.Wrap(layer2Err) // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.InvalidFormat, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") // errors.Is should report correctly assert.True(t, errors.Is(e, layer1Err)) assert.True(t, errors.Is(e, layer2Err)) } func TestStructErr_WithInternalErr_ShouldErrorOutputChainErrMessage(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() // arrange 2 layers err layer1Err := fmt.Errorf("layer 1 error") // act with error chain: InvalidFormat -> layer 1 err e := InvalidFormat("field A", "Error description") e.Wrap(layer1Err) // assert assert.Equal(t, "invalid format: field A Error description: layer 1 error", e.Error()) } // arrange a specific err type just for UT type testErr struct { code int } func (e *testErr) Error() string { return strconv.Itoa(e.code) } func TestStructErr_WithInternalErr_ShouldAsFuncReportCorrectly(t *testing.T) { // setup Scope = 99 defer func() { Scope = code.Unset }() testE := &testErr{code: 123} layer2Err := fmt.Errorf("layer 2: %w", testE) // act with error chain: InvalidFormat -> layer 2 err -> testErr e := InvalidFormat("field A", "Error description") e.Wrap(layer2Err) // assert assert.Equal(t, code.CatInput, e.Category()) assert.Equal(t, code.InvalidFormat, e.Code()) assert.Equal(t, uint32(99), e.Scope()) assert.Contains(t, e.Error(), "field A") assert.Contains(t, e.Error(), "Error description") // errors.As should report correctly var internalErr *testErr assert.True(t, errors.As(e, &internalErr)) assert.Equal(t, testE, internalErr) } /* benchmark run for 1 second: Benchmark_ErrorsIs_OneLayerError-4 148281332 8.68 ns/op 0 B/op 0 allocs/op Benchmark_ErrorsIs_TwoLayerError-4 35048202 32.4 ns/op 0 B/op 0 allocs/op Benchmark_ErrorsIs_FourLayerError-4 15309349 81.7 ns/op 0 B/op 0 allocs/op Benchmark_ErrorsAs_OneLayerError-4 16893205 70.4 ns/op 0 B/op 0 allocs/op Benchmark_ErrorsAs_TwoLayerError-4 10568083 112 ns/op 0 B/op 0 allocs/op Benchmark_ErrorsAs_FourLayerError-4 6307729 188 ns/op 0 B/op 0 allocs/op */ func Benchmark_ErrorsIs_OneLayerError(b *testing.B) { layer1Err := &testErr{code: 123} var err error = layer1Err b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { errors.Is(err, layer1Err) } } func Benchmark_ErrorsIs_TwoLayerError(b *testing.B) { layer1Err := &testErr{code: 123} // act with error chain: InvalidFormat(layer 2) -> testErr(layer 1) layer2Err := InvalidFormat("field A", "Error description") layer2Err.Wrap(layer1Err) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { errors.Is(layer2Err, layer1Err) } } func Benchmark_ErrorsIs_FourLayerError(b *testing.B) { layer1Err := &testErr{code: 123} layer2Err := fmt.Errorf("layer 2: %w", layer1Err) layer3Err := fmt.Errorf("layer 3: %w", layer2Err) // act with error chain: InvalidFormat(layer 4) -> Error(layer 3) -> Error(layer 2) -> testErr(layer 1) layer4Err := InvalidFormat("field A", "Error description") layer4Err.Wrap(layer3Err) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { errors.Is(layer4Err, layer1Err) } } func Benchmark_ErrorsAs_OneLayerError(b *testing.B) { layer1Err := &testErr{code: 123} var err error = layer1Err b.ReportAllocs() b.ResetTimer() var internalErr *testErr for i := 0; i < b.N; i++ { errors.As(err, &internalErr) } } func Benchmark_ErrorsAs_TwoLayerError(b *testing.B) { layer1Err := &testErr{code: 123} // act with error chain: InvalidFormat(layer 2) -> testErr(layer 1) layer2Err := InvalidFormat("field A", "Error description") layer2Err.Wrap(layer1Err) b.ReportAllocs() b.ResetTimer() var internalErr *testErr for i := 0; i < b.N; i++ { errors.As(layer2Err, &internalErr) } } func Benchmark_ErrorsAs_FourLayerError(b *testing.B) { layer1Err := &testErr{code: 123} layer2Err := fmt.Errorf("layer 2: %w", layer1Err) layer3Err := fmt.Errorf("layer 3: %w", layer2Err) // act with error chain: InvalidFormat(layer 4) -> Error(layer 3) -> Error(layer 2) -> testErr(layer 1) layer4Err := InvalidFormat("field A", "Error description") layer4Err.Wrap(layer3Err) b.ReportAllocs() b.ResetTimer() var internalErr *testErr for i := 0; i < b.N; i++ { errors.As(layer4Err, &internalErr) } } func TestFromError(t *testing.T) { tests := []struct { name string givenError error want *Err }{ { "given nil error should return nil", nil, nil, }, { "given normal error should return nil", errors.New("normal error"), nil, }, { "given Err should return Err", ResourceNotFound("fake error"), ResourceNotFound("fake error"), }, { "given error wraps Err should return Err", fmt.Errorf("outter error wraps %w", ResourceNotFound("fake error")), ResourceNotFound("fake error"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := FromError(tt.givenError); !reflect.DeepEqual(got, tt.want) { t.Errorf("FromError() = %v, want %v", got, tt.want) } }) } }