This is an automated email from the ASF dual-hosted git repository.
joaoreis pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-gocql-driver.git
The following commit(s) were added to refs/heads/trunk by this push:
new b86c662e Add dedicated error types for all generic server error codes
b86c662e is described below
commit b86c662e14e223d2c85b68c05b537f52e25ed9d6
Author: Paolo Elefante <[email protected]>
AuthorDate: Thu Mar 19 00:08:36 2026 +0100
Add dedicated error types for all generic server error codes
Introduce distinct concrete types for all server error codes that were
previously returned as generic errorFrame values:
RequestErrOverloaded, RequestErrBootstrapping, RequestErrInvalid,
RequestErrConfig, RequestErrCredentials, RequestErrProtocol,
RequestErrServer, RequestErrSyntax, RequestErrTruncate,
RequestErrUnauthorized
Each type embeds errorFrame and follows the existing pattern used for
RequestErrUnavailable, RequestErrWriteTimeout, RequestErrReadTimeout.
The catch-all case and the TODO comment in parseErrorFrame are removed.
Add tests covering parsing of all ten error codes.
Patch by Paolo Elefante; reviewed by João Reis, Bohdan Siryk for CASSGO-113
---
CHANGELOG.md | 1 +
errors.go | 40 +++++++++
frame.go | 20 ++++-
frame_test.go | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 312 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcbbca73..c064c600 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- Support for session ready, host state, topology change and schema changes
custom listeners (CASSGO-101)
- Add Session.AllKeyspaceMetadata() (CASSGO-109)
- Add GetSerialConsistency method to Query and Batch (CASSGO-103)
+- Add RequestErrOverloaded, RequestErrBootstrapping, RequestErrInvalid,
RequestErrConfig, RequestErrCredentials, RequestErrSyntax, RequestErrTruncate,
RequestErrUnauthorized for dedicated error handling (CASSGO-113)
### Changed
diff --git a/errors.go b/errors.go
index 4305f78f..95aeb907 100644
--- a/errors.go
+++ b/errors.go
@@ -154,6 +154,46 @@ func (e *RequestErrUnavailable) String() string {
return fmt.Sprintf("[request_error_unavailable consistency=%s
required=%d alive=%d]", e.Consistency, e.Required, e.Alive)
}
+// RequestErrOverloaded represents an overloaded error returned by Cassandra.
+type RequestErrOverloaded struct {
+ errorFrame
+}
+
+// RequestErrBootstrapping represents a bootstrapping error returned by
Cassandra.
+type RequestErrBootstrapping struct {
+ errorFrame
+}
+
+// RequestErrInvalid represents an invalid query error returned by Cassandra.
+type RequestErrInvalid struct {
+ errorFrame
+}
+
+// RequestErrConfig represents a configuration error returned by Cassandra.
+type RequestErrConfig struct {
+ errorFrame
+}
+
+// RequestErrCredentials represents a credentials error returned by Cassandra.
+type RequestErrCredentials struct {
+ errorFrame
+}
+
+// RequestErrSyntax represents a syntax error returned by Cassandra.
+type RequestErrSyntax struct {
+ errorFrame
+}
+
+// RequestErrTruncate represents a truncation error returned by Cassandra.
+type RequestErrTruncate struct {
+ errorFrame
+}
+
+// RequestErrUnauthorized represents an unauthorized error returned by
Cassandra.
+type RequestErrUnauthorized struct {
+ errorFrame
+}
+
// ErrorMap maps node IP addresses to their respective error codes for
read/write failure responses.
// Each entry represents a node that failed during the operation, with the key
being the node's
// IP address as a string and the value being the specific error code returned
by that node.
diff --git a/frame.go b/frame.go
index c9790b0a..7ad118c9 100644
--- a/frame.go
+++ b/frame.go
@@ -759,10 +759,24 @@ func (f *framer) parseErrorFrame() (frame, error) {
return nil, err
}
return res, nil
- case ErrCodeInvalid, ErrCodeBootstrapping, ErrCodeConfig,
ErrCodeCredentials, ErrCodeOverloaded,
- ErrCodeProtocol, ErrCodeServer, ErrCodeSyntax, ErrCodeTruncate,
ErrCodeUnauthorized:
- // TODO(zariel): we should have some distinct types for these
errors
+ case ErrCodeOverloaded:
+ return &RequestErrOverloaded{errorFrame: errD}, nil
+ case ErrCodeBootstrapping:
+ return &RequestErrBootstrapping{errorFrame: errD}, nil
+ case ErrCodeInvalid:
+ return &RequestErrInvalid{errorFrame: errD}, nil
+ case ErrCodeConfig:
+ return &RequestErrConfig{errorFrame: errD}, nil
+ case ErrCodeCredentials:
+ return &RequestErrCredentials{errorFrame: errD}, nil
+ case ErrCodeServer, ErrCodeProtocol:
return errD, nil
+ case ErrCodeSyntax:
+ return &RequestErrSyntax{errorFrame: errD}, nil
+ case ErrCodeTruncate:
+ return &RequestErrTruncate{errorFrame: errD}, nil
+ case ErrCodeUnauthorized:
+ return &RequestErrUnauthorized{errorFrame: errD}, nil
default:
return nil, fmt.Errorf("unknown error code: 0x%x", errD.code)
}
diff --git a/frame_test.go b/frame_test.go
index 26430beb..0bd7edad 100644
--- a/frame_test.go
+++ b/frame_test.go
@@ -893,3 +893,257 @@ func Test_newFrame_compressionFlag(t *testing.T) {
})
}
}
+
+func newErrorFrameForTest(code int, msg string) *framer {
+ f := newFramer(nil, protoVersion4, GlobalTypes)
+ f.header = &frameHeader{
+ version: protoVersion4 | protoDirectionMask,
+ stream: 1,
+ op: opError,
+ }
+ f.writeInt(int32(code))
+ f.writeString(msg)
+ return f
+}
+
+func TestParseErrorFrameDedicatedTypes(t *testing.T) {
+ tests := []struct {
+ name string
+ code int
+ msg string
+ assertT func(*testing.T, frame)
+ }{
+ {
+ name: "overloaded",
+ code: ErrCodeOverloaded,
+ msg: "coordinator overloaded",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrOverloaded)
+ if !ok {
+ t.Fatalf("expected
*RequestErrOverloaded, got %T", got)
+ }
+ if reqErr.Code() != ErrCodeOverloaded {
+ t.Fatalf("expected code %x, got %x",
ErrCodeOverloaded, reqErr.Code())
+ }
+ if reqErr.Message() != "coordinator overloaded"
{
+ t.Fatalf("expected message %q, got %q",
"coordinator overloaded", reqErr.Message())
+ }
+ if reqErr.Error() != "coordinator overloaded" {
+ t.Fatalf("expected error string %q, got
%q", "coordinator overloaded", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "bootstrapping",
+ code: ErrCodeBootstrapping,
+ msg: "node is bootstrapping",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrBootstrapping)
+ if !ok {
+ t.Fatalf("expected
*RequestErrBootstrapping, got %T", got)
+ }
+ if reqErr.Code() != ErrCodeBootstrapping {
+ t.Fatalf("expected code %x, got %x",
ErrCodeBootstrapping, reqErr.Code())
+ }
+ if reqErr.Message() != "node is bootstrapping" {
+ t.Fatalf("expected message %q, got %q",
"node is bootstrapping", reqErr.Message())
+ }
+ if reqErr.Error() != "node is bootstrapping" {
+ t.Fatalf("expected error string %q, got
%q", "node is bootstrapping", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "invalid",
+ code: ErrCodeInvalid,
+ msg: "invalid query",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrInvalid)
+ if !ok {
+ t.Fatalf("expected *RequestErrInvalid,
got %T", got)
+ }
+ if reqErr.Code() != ErrCodeInvalid {
+ t.Fatalf("expected code %x, got %x",
ErrCodeInvalid, reqErr.Code())
+ }
+ if reqErr.Message() != "invalid query" {
+ t.Fatalf("expected message %q, got %q",
"invalid query", reqErr.Message())
+ }
+ if reqErr.Error() != "invalid query" {
+ t.Fatalf("expected error string %q, got
%q", "invalid query", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "config",
+ code: ErrCodeConfig,
+ msg: "configuration error",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrConfig)
+ if !ok {
+ t.Fatalf("expected *RequestErrConfig,
got %T", got)
+ }
+ if reqErr.Code() != ErrCodeConfig {
+ t.Fatalf("expected code %x, got %x",
ErrCodeConfig, reqErr.Code())
+ }
+ if reqErr.Message() != "configuration error" {
+ t.Fatalf("expected message %q, got %q",
"configuration error", reqErr.Message())
+ }
+ if reqErr.Error() != "configuration error" {
+ t.Fatalf("expected error string %q, got
%q", "configuration error", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "credentials",
+ code: ErrCodeCredentials,
+ msg: "bad credentials",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrCredentials)
+ if !ok {
+ t.Fatalf("expected
*RequestErrCredentials, got %T", got)
+ }
+ if reqErr.Code() != ErrCodeCredentials {
+ t.Fatalf("expected code %x, got %x",
ErrCodeCredentials, reqErr.Code())
+ }
+ if reqErr.Message() != "bad credentials" {
+ t.Fatalf("expected message %q, got %q",
"bad credentials", reqErr.Message())
+ }
+ if reqErr.Error() != "bad credentials" {
+ t.Fatalf("expected error string %q, got
%q", "bad credentials", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "syntax",
+ code: ErrCodeSyntax,
+ msg: "syntax error",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrSyntax)
+ if !ok {
+ t.Fatalf("expected *RequestErrSyntax,
got %T", got)
+ }
+ if reqErr.Code() != ErrCodeSyntax {
+ t.Fatalf("expected code %x, got %x",
ErrCodeSyntax, reqErr.Code())
+ }
+ if reqErr.Message() != "syntax error" {
+ t.Fatalf("expected message %q, got %q",
"syntax error", reqErr.Message())
+ }
+ if reqErr.Error() != "syntax error" {
+ t.Fatalf("expected error string %q, got
%q", "syntax error", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "truncate",
+ code: ErrCodeTruncate,
+ msg: "truncation error",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrTruncate)
+ if !ok {
+ t.Fatalf("expected *RequestErrTruncate,
got %T", got)
+ }
+ if reqErr.Code() != ErrCodeTruncate {
+ t.Fatalf("expected code %x, got %x",
ErrCodeTruncate, reqErr.Code())
+ }
+ if reqErr.Message() != "truncation error" {
+ t.Fatalf("expected message %q, got %q",
"truncation error", reqErr.Message())
+ }
+ if reqErr.Error() != "truncation error" {
+ t.Fatalf("expected error string %q, got
%q", "truncation error", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ {
+ name: "unauthorized",
+ code: ErrCodeUnauthorized,
+ msg: "unauthorized",
+ assertT: func(t *testing.T, got frame) {
+ reqErr, ok := got.(*RequestErrUnauthorized)
+ if !ok {
+ t.Fatalf("expected
*RequestErrUnauthorized, got %T", got)
+ }
+ if reqErr.Code() != ErrCodeUnauthorized {
+ t.Fatalf("expected code %x, got %x",
ErrCodeUnauthorized, reqErr.Code())
+ }
+ if reqErr.Message() != "unauthorized" {
+ t.Fatalf("expected message %q, got %q",
"unauthorized", reqErr.Message())
+ }
+ if reqErr.Error() != "unauthorized" {
+ t.Fatalf("expected error string %q, got
%q", "unauthorized", reqErr.Error())
+ }
+ if reqErr.Header().op != opError {
+ t.Fatalf("expected op %v, got %v",
opError, reqErr.Header().op)
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ f := newErrorFrameForTest(tt.code, tt.msg)
+
+ got, err := f.parseErrorFrame()
+ if err != nil {
+ t.Fatalf("parseErrorFrame returned error: %v",
err)
+ }
+
+ tt.assertT(t, got)
+ })
+ }
+}
+
+func TestParseErrorFrameAllGenericCodes(t *testing.T) {
+ codes := []struct {
+ name string
+ code int
+ }{
+ {"overloaded", ErrCodeOverloaded},
+ {"bootstrapping", ErrCodeBootstrapping},
+ {"invalid", ErrCodeInvalid},
+ {"config", ErrCodeConfig},
+ {"credentials", ErrCodeCredentials},
+ {"syntax", ErrCodeSyntax},
+ {"truncate", ErrCodeTruncate},
+ {"unauthorized", ErrCodeUnauthorized},
+ }
+
+ for _, tc := range codes {
+ t.Run(tc.name, func(t *testing.T) {
+ f := newErrorFrameForTest(tc.code, "test message")
+
+ got, err := f.parseErrorFrame()
+ if err != nil {
+ t.Fatalf("parseErrorFrame returned error: %v",
err)
+ }
+
+ reqErr, ok := got.(RequestError)
+ if !ok {
+ t.Fatalf("expected RequestError, got %T", got)
+ }
+ if reqErr.Code() != tc.code {
+ t.Fatalf("expected code %x, got %x", tc.code,
reqErr.Code())
+ }
+ })
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]