diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a595555..b87f438b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +* Added nodeID and address to operation and transport errors * Prevented create decoder instance until start read a message from topics ## v3.99.4 diff --git a/internal/xerrors/operation.go b/internal/xerrors/operation.go index bc0e9e997..2b957811d 100644 --- a/internal/xerrors/operation.go +++ b/internal/xerrors/operation.go @@ -28,6 +28,14 @@ func (e *operationError) Code() int32 { return int32(e.code) } +func (e *operationError) NodeID() uint32 { + return e.nodeID +} + +func (e *operationError) Address() string { + return e.address +} + func (e *operationError) Name() string { return "operation/" + e.code.String() } diff --git a/internal/xerrors/operation_test.go b/internal/xerrors/operation_test.go index 41d735957..6de4fadb0 100644 --- a/internal/xerrors/operation_test.go +++ b/internal/xerrors/operation_test.go @@ -7,24 +7,30 @@ import ( "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" ) func TestIsOperationError(t *testing.T) { for _, tt := range []struct { + name string err error codes []Ydb.StatusIds_StatusCode match bool }{ // check only operation error with any ydb status code { + name: xtest.CurrentFileLine(), err: &operationError{code: Ydb.StatusIds_BAD_REQUEST}, match: true, }, { + name: xtest.CurrentFileLine(), err: fmt.Errorf("wrapped: %w", &operationError{code: Ydb.StatusIds_BAD_REQUEST}), match: true, }, { + name: xtest.CurrentFileLine(), err: Join( fmt.Errorf("test"), &operationError{code: Ydb.StatusIds_BAD_REQUEST}, @@ -34,16 +40,19 @@ func TestIsOperationError(t *testing.T) { }, // match ydb status code { + name: xtest.CurrentFileLine(), err: &operationError{code: Ydb.StatusIds_BAD_REQUEST}, codes: []Ydb.StatusIds_StatusCode{Ydb.StatusIds_BAD_REQUEST}, match: true, }, { + name: xtest.CurrentFileLine(), err: fmt.Errorf("wrapped: %w", &operationError{code: Ydb.StatusIds_BAD_REQUEST}), codes: []Ydb.StatusIds_StatusCode{Ydb.StatusIds_BAD_REQUEST}, match: true, }, { + name: xtest.CurrentFileLine(), err: Join( fmt.Errorf("test"), &operationError{code: Ydb.StatusIds_BAD_REQUEST}, @@ -54,16 +63,19 @@ func TestIsOperationError(t *testing.T) { }, // no match ydb status code { + name: xtest.CurrentFileLine(), err: &operationError{code: Ydb.StatusIds_BAD_REQUEST}, codes: []Ydb.StatusIds_StatusCode{Ydb.StatusIds_ABORTED}, match: false, }, { + name: xtest.CurrentFileLine(), err: fmt.Errorf("wrapped: %w", &operationError{code: Ydb.StatusIds_BAD_REQUEST}), codes: []Ydb.StatusIds_StatusCode{Ydb.StatusIds_ABORTED}, match: false, }, { + name: xtest.CurrentFileLine(), err: Join( fmt.Errorf("test"), &operationError{code: Ydb.StatusIds_BAD_REQUEST}, @@ -73,7 +85,7 @@ func TestIsOperationError(t *testing.T) { match: false, }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.match, IsOperationError(tt.err, tt.codes...)) }) } @@ -81,10 +93,12 @@ func TestIsOperationError(t *testing.T) { func TestIsOperationErrorTransactionLocksInvalidated(t *testing.T) { for _, tt := range [...]struct { + name string err error isTLI bool }{ { + name: xtest.CurrentFileLine(), err: Operation( WithStatusCode(Ydb.StatusIds_ABORTED), WithIssues([]*Ydb_Issue.IssueMessage{{ @@ -94,6 +108,7 @@ func TestIsOperationErrorTransactionLocksInvalidated(t *testing.T) { isTLI: true, }, { + name: xtest.CurrentFileLine(), err: Operation( WithStatusCode(Ydb.StatusIds_OVERLOADED), WithIssues([]*Ydb_Issue.IssueMessage{{ @@ -103,12 +118,14 @@ func TestIsOperationErrorTransactionLocksInvalidated(t *testing.T) { isTLI: false, }, { + name: xtest.CurrentFileLine(), err: Operation( WithStatusCode(Ydb.StatusIds_ABORTED), ), isTLI: false, }, { + name: xtest.CurrentFileLine(), err: Operation( WithStatusCode(Ydb.StatusIds_ABORTED), WithIssues([]*Ydb_Issue.IssueMessage{{ @@ -120,7 +137,7 @@ func TestIsOperationErrorTransactionLocksInvalidated(t *testing.T) { isTLI: true, }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.isTLI, IsOperationErrorTransactionLocksInvalidated(tt.err)) }) } @@ -128,22 +145,32 @@ func TestIsOperationErrorTransactionLocksInvalidated(t *testing.T) { func Test_operationError_Error(t *testing.T) { for _, tt := range []struct { + name string err error text string }{ { + name: xtest.CurrentFileLine(), err: Operation(WithStatusCode(Ydb.StatusIds_BAD_REQUEST), WithAddress("localhost")), text: "operation/BAD_REQUEST (code = 400010, address = localhost)", }, { + name: xtest.CurrentFileLine(), + err: Operation(WithStatusCode(Ydb.StatusIds_BAD_REQUEST), WithNodeID(100500)), + text: "operation/BAD_REQUEST (code = 400010, nodeID = 100500)", + }, + { + name: xtest.CurrentFileLine(), err: Operation(WithStatusCode(Ydb.StatusIds_BAD_REQUEST)), text: "operation/BAD_REQUEST (code = 400010)", }, { + name: xtest.CurrentFileLine(), err: Operation(WithStatusCode(Ydb.StatusIds_BAD_SESSION)), text: "operation/BAD_SESSION (code = 400100)", }, { + name: xtest.CurrentFileLine(), err: Operation(WithStatusCode(Ydb.StatusIds_PRECONDITION_FAILED), WithIssues([]*Ydb_Issue.IssueMessage{ { Message: "issue one", @@ -177,7 +204,7 @@ func Test_operationError_Error(t *testing.T) { text: "operation/PRECONDITION_FAILED (code = 400120, issues = [{15:3 => #1 'issue one'},{#2 'issue two' [{test.yql:16:4 => #3 'issue three'},{#4 'issue four'}]}])", //nolint:lll }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.text, tt.err.Error()) }) } diff --git a/internal/xerrors/transport.go b/internal/xerrors/transport.go index 8700652b1..16776df02 100644 --- a/internal/xerrors/transport.go +++ b/internal/xerrors/transport.go @@ -27,6 +27,14 @@ func (e *transportError) GRPCStatus() *grpcStatus.Status { func (e *transportError) isYdbError() {} +func (e *transportError) NodeID() uint32 { + return e.nodeID +} + +func (e *transportError) Address() string { + return e.address +} + func (e *transportError) Code() int32 { return int32(e.status.Code()) } @@ -134,8 +142,8 @@ func IsTransportError(err error, codes ...grpcCodes.Code) bool { var status *grpcStatus.Status if t := (*transportError)(nil); errors.As(err, &t) { status = t.status - } else if t, has := grpcStatus.FromError(err); has { - status = t + } else if s, has := grpcStatus.FromError(err); has { + status = s } if status != nil { if len(codes) == 0 { diff --git a/internal/xerrors/transport_test.go b/internal/xerrors/transport_test.go index bfb81fa86..4ec468ab1 100644 --- a/internal/xerrors/transport_test.go +++ b/internal/xerrors/transport_test.go @@ -145,19 +145,27 @@ func TestGrpcError(t *testing.T) { func TestTransportErrorString(t *testing.T) { for _, tt := range []struct { + name string err error text string }{ { + name: xtest.CurrentFileLine(), err: Transport(grpcStatus.Error(grpcCodes.FailedPrecondition, "")), text: "transport/FailedPrecondition (code = 9, source error = \"rpc error: code = FailedPrecondition desc = \")", }, { + name: xtest.CurrentFileLine(), err: Transport(grpcStatus.Error(grpcCodes.Unavailable, ""), WithAddress("localhost:2135")), text: "transport/Unavailable (code = 14, source error = \"rpc error: code = Unavailable desc = \", address: \"localhost:2135\")", //nolint:lll }, + { + name: xtest.CurrentFileLine(), + err: Transport(grpcStatus.Error(grpcCodes.Unavailable, ""), WithNodeID(100500)), + text: "transport/Unavailable (code = 14, source error = \"rpc error: code = Unavailable desc = \", nodeID = 100500)", //nolint:lll + }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.text, tt.err.Error()) }) } @@ -189,7 +197,7 @@ func TestTransportErrorName(t *testing.T) { name: "transport/Aborted", }, } { - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { if tt.err == nil { require.Nil(t, TransportError(tt.err)) //nolint:testifylint } else {