• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tarantool / go-tarantool / 27108674847

07 Jun 2026 11:56PM UTC coverage: 75.446% (+0.2%) from 75.212%
27108674847

push

github

oleg-jukovec
api: redesign errors around errors.Is and errors.As

Rename tarantool.Error to ServerError, promote the seven legacy uint32
client error codes to package-level error sentinels (ErrConnectionClosed,
ErrTimeouted, ...) and expose their numeric form as iproto.Error
constants (CodeConnectionClosed, ...), the same type carried by
ServerError.Code, so client and server codes compare without
conversion. ClientError gains a Cause field plus Unwrap() []error so
errors.Is matches the sentinel and errors.As reaches the underlying net
error. Retryability is expressed as a sentinel (ErrRetryable) joined
into the chain; IsRetryableError wraps errors.Is, and each code is
documented with whether the request is safe to repeat. The deprecated
Temporary() method is removed.

Internal callers in connection.go, dial.go and response.go switched to
newClientError / ServerError detection; I/O failures now wrap the
original error instead of stringifying it. Tests and examples in box,
arrow, pool, and the root package were migrated from type assertions to
errors.As / errors.Is.

Also fixes a pre-existing %x formatting bug on iproto.Error in
ServerError.Error(); ClientError.Error() formats its code the same way.

Closes #469

63 of 97 new or added lines in 4 files covered. (64.95%)

2 existing lines in 1 file now uncovered.

3128 of 4146 relevant lines covered (75.45%)

10104.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.44
/errors.go
1
package tarantool
2

3
import (
4
        "errors"
5
        "fmt"
6
        "strings"
7

8
        "github.com/tarantool/go-iproto"
9
)
10

11
// Client error codes produced by this client itself, as opposed to the
12
// server-side iproto.Error codes carried by ServerError. The numeric
13
// values match the constants exposed by previous versions of
14
// go-tarantool. They share the iproto.Error type so comparisons with
15
// go-iproto values need no conversion.
16
//
17
// Each code is annotated with whether the failed request is safe to
18
// repeat without side effects. Codes marked retryable are joined with
19
// ErrRetryable in the error chain, so errors.Is(err, ErrRetryable) and
20
// IsRetryableError(err) report true for them.
21
const (
22
        // CodeConnectionNotReady: the connection was not established, so the
23
        // request never reached the server. Safe to repeat (retryable).
24
        CodeConnectionNotReady iproto.Error = 0x4000 + iota
25
        // CodeConnectionClosed: the connection was closed; the request may or
26
        // may not have been sent. Not retryable.
27
        CodeConnectionClosed
28
        // CodeProtocolError: the server reply could not be decoded. Not
29
        // retryable.
30
        CodeProtocolError
31
        // CodeTimeouted: the request timed out in flight and may already be
32
        // executing on the server, so repeating it can duplicate side effects.
33
        // It is nonetheless flagged retryable to preserve the historical
34
        // transient-failure handling; callers that need exactly-once semantics
35
        // must guard non-idempotent requests themselves.
36
        CodeTimeouted
37
        // CodeRateLimited: the request was rejected locally before being sent
38
        // because of the rate limit. Safe to repeat (retryable).
39
        CodeRateLimited
40
        // CodeConnectionShutdown: the request was rejected because the server
41
        // signalled graceful shutdown. Not retryable on this connection.
42
        CodeConnectionShutdown
43
        // CodeIoError: an I/O error occurred on the socket. Retryable.
44
        CodeIoError
45
)
46

47
// sentinelError is a package-level error value matched via errors.Is.
48
type sentinelError struct {
49
        code iproto.Error
50
        msg  string
51
}
52

53
func (s *sentinelError) Error() string      { return s.msg }
11✔
NEW
54
func (s *sentinelError) Code() iproto.Error { return s.code }
×
55

56
// Sentinel errors for client-side failure modes. Compare with errors.Is.
57
var (
58
        ErrConnectionNotReady error = &sentinelError{CodeConnectionNotReady, "connection not ready"}
59
        ErrConnectionClosed   error = &sentinelError{CodeConnectionClosed, "connection closed"}
60
        ErrProtocolError      error = &sentinelError{CodeProtocolError, "protocol error"}
61
        ErrTimeouted          error = &sentinelError{CodeTimeouted, "request timed out"}
62
        ErrRateLimited        error = &sentinelError{CodeRateLimited, "rate limited"}
63
        ErrConnectionShutdown error = &sentinelError{CodeConnectionShutdown, "connection shutdown"}
64
        ErrIoError            error = &sentinelError{CodeIoError, "I/O error"}
65

66
        // ErrRetryable marks errors that may succeed on retry. It is
67
        // joined into the error chain of any retryable ClientError so
68
        // that errors.Is(err, ErrRetryable) returns true.
69
        ErrRetryable = errors.New("retryable")
70
)
71

72
// codeToSentinel maps a code to its package-level sentinel.
73
var codeToSentinel = map[iproto.Error]error{
74
        CodeConnectionNotReady: ErrConnectionNotReady,
75
        CodeConnectionClosed:   ErrConnectionClosed,
76
        CodeProtocolError:      ErrProtocolError,
77
        CodeTimeouted:          ErrTimeouted,
78
        CodeRateLimited:        ErrRateLimited,
79
        CodeConnectionShutdown: ErrConnectionShutdown,
80
        CodeIoError:            ErrIoError,
81
}
82

83
// retryableSentinels lists the codes whose chain implies ErrRetryable.
84
var retryableSentinels = map[iproto.Error]struct{}{
85
        CodeConnectionNotReady: {},
86
        CodeTimeouted:          {},
87
        CodeRateLimited:        {},
88
        CodeIoError:            {},
89
}
90

91
// ClientError is a failure produced by this client: connection state
92
// transitions, request timeouts, protocol decoding, or I/O.
93
//
94
// Compare with package sentinels via errors.Is. If Cause is set, it
95
// is reachable via errors.As / errors.Unwrap.
96
type ClientError struct {
97
        Code  iproto.Error
98
        Msg   string
99
        Cause error
100
}
101

102
// Error formats as "<sentinel>: <Msg>: <cause>", omitting any empty
103
// segment. If the code has no registered sentinel, the prefix falls
104
// back to "client error 0x<code>".
105
func (e ClientError) Error() string {
12✔
106
        parts := make([]string, 0, 3)
12✔
107
        if s, ok := codeToSentinel[e.Code]; ok {
23✔
108
                parts = append(parts, s.Error())
11✔
109
        } else {
12✔
110
                parts = append(parts, fmt.Sprintf("client error 0x%x", int(e.Code)))
1✔
111
        }
1✔
112
        if e.Msg != "" && parts[0] != e.Msg {
23✔
113
                parts = append(parts, e.Msg)
11✔
114
        }
11✔
115
        if e.Cause != nil {
14✔
116
                parts = append(parts, e.Cause.Error())
2✔
117
        }
2✔
118
        return strings.Join(parts, ": ")
12✔
119
}
120

121
// Unwrap exposes the sentinel, ErrRetryable (when applicable), and
122
// the underlying cause to errors.Is / errors.As.
123
func (e ClientError) Unwrap() []error {
94✔
124
        out := make([]error, 0, 3)
94✔
125
        if s, ok := codeToSentinel[e.Code]; ok {
188✔
126
                out = append(out, s)
94✔
127
        }
94✔
128
        if _, ok := retryableSentinels[e.Code]; ok {
110✔
129
                out = append(out, ErrRetryable)
16✔
130
        }
16✔
131
        if e.Cause != nil {
97✔
132
                out = append(out, e.Cause)
3✔
133
        }
3✔
134
        return out
94✔
135
}
136

137
// newClientError is the internal constructor used across the package.
138
func newClientError(code iproto.Error, msg string, cause error) ClientError {
806✔
139
        return ClientError{Code: code, Msg: msg, Cause: cause}
806✔
140
}
806✔
141

142
// ServerError wraps an error returned by the Tarantool server.
143
type ServerError struct {
144
        Code         iproto.Error
145
        Msg          string
146
        ExtendedInfo *BoxError
147
}
148

149
// Error converts a ServerError to a string.
150
func (e ServerError) Error() string {
7✔
151
        if e.ExtendedInfo != nil {
13✔
152
                return e.ExtendedInfo.Error()
6✔
153
        }
6✔
154
        return fmt.Sprintf("%s (0x%x)", e.Msg, int(e.Code))
1✔
155
}
156

157
// IsRetryableError reports whether err indicates a transient failure
158
// that may succeed on retry.
159
func IsRetryableError(err error) bool {
7✔
160
        return errors.Is(err, ErrRetryable)
7✔
161
}
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc