Skip to content

Commit 095bf5a

Browse files
api: support errors extended information
Since Tarantool 2.4.1, iproto error responses contain extended info with backtrace [1]. After this patch, Error would contain ExtendedInfo field (BoxError object), if it was provided. Error() handle now will print extended info, if possible. 1. https://www.tarantool.io/en/doc/latest/dev_guide/internals/box_protocol/#responses-for-errors Part of #209
1 parent dcc1891 commit 095bf5a

File tree

8 files changed

+389
-5
lines changed

8 files changed

+389
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1111
### Added
1212

1313
- Support iproto feature discovery (#120).
14+
- Support errors extended information (#209).
1415

1516
### Changed
1617

box_error.go

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package tarantool
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// BoxError is a type representing Tarantool `box.error` object: a single
8+
// MP_ERROR_STACK object with a link to the previous stack error.
9+
// See https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_error/error/
10+
//
11+
// Since 1.10.0
12+
type BoxError struct {
13+
// Type is error type that implies its source (for example, "ClientError").
14+
Type string
15+
// File is a source code file where the error was caught.
16+
File string
17+
// Line is a number of line in the source code file where the error was caught.
18+
Line uint32
19+
// Msg is the text of reason.
20+
Msg string
21+
// Errno is the ordinal number of the error.
22+
Errno uint32
23+
// Code is the number of the error as defined in `errcode.h`.
24+
Code uint32
25+
// Fields are additional fields depending on error type. For example, if
26+
// type is "AccessDeniedError", then it will include "object_type",
27+
// "object_name", "access_type".
28+
Fields map[interface{}]interface{}
29+
// Prev is the previous error in stack.
30+
Prev *BoxError
31+
}
32+
33+
// Error converts a BoxError to a string.
34+
func (boxError *BoxError) Error() string {
35+
return fmt.Sprintf("%s (%s, code 0x%x), see %s:%d",
36+
boxError.Msg, boxError.Type, boxError.Code,
37+
boxError.File, boxError.Line)
38+
}
39+
40+
// Depth computes the count of errors in stack, including the current one.
41+
//
42+
// Since 1.10.0
43+
func (boxError *BoxError) Depth() (depth int) {
44+
cur := boxError
45+
46+
depth = 0
47+
48+
for cur != nil {
49+
cur = cur.Prev
50+
depth++
51+
}
52+
53+
return depth
54+
}
55+
56+
func decodeBoxError(d *decoder) (*BoxError, error) {
57+
var l, larr, l1, l2 int
58+
var errorStack []BoxError
59+
var err error
60+
var mapk, mapv interface{}
61+
62+
if l, err = d.DecodeMapLen(); err != nil {
63+
return nil, err
64+
}
65+
66+
for ; l > 0; l-- {
67+
var cd int
68+
if cd, err = d.DecodeInt(); err != nil {
69+
return nil, err
70+
}
71+
switch cd {
72+
case KeyErrorStack:
73+
if larr, err = d.DecodeArrayLen(); err != nil {
74+
return nil, err
75+
}
76+
77+
errorStack = make([]BoxError, larr)
78+
79+
for i := 0; i < larr; i++ {
80+
if l1, err = d.DecodeMapLen(); err != nil {
81+
return nil, err
82+
}
83+
84+
for ; l1 > 0; l1-- {
85+
var cd1 int
86+
if cd1, err = d.DecodeInt(); err != nil {
87+
return nil, err
88+
}
89+
switch cd1 {
90+
case KeyErrorType:
91+
if errorStack[i].Type, err = d.DecodeString(); err != nil {
92+
return nil, err
93+
}
94+
case KeyErrorFile:
95+
if errorStack[i].File, err = d.DecodeString(); err != nil {
96+
return nil, err
97+
}
98+
case KeyErrorLine:
99+
if errorStack[i].Line, err = d.DecodeUint32(); err != nil {
100+
return nil, err
101+
}
102+
case KeyErrorMessage:
103+
if errorStack[i].Msg, err = d.DecodeString(); err != nil {
104+
return nil, err
105+
}
106+
case KeyErrorErrno:
107+
if errorStack[i].Errno, err = d.DecodeUint32(); err != nil {
108+
return nil, err
109+
}
110+
case KeyErrorErrcode:
111+
if errorStack[i].Code, err = d.DecodeUint32(); err != nil {
112+
return nil, err
113+
}
114+
case KeyErrorFields:
115+
errorStack[i].Fields = make(map[interface{}]interface{})
116+
if l2, err = d.DecodeMapLen(); err != nil {
117+
return nil, err
118+
}
119+
for ; l2 > 0; l2-- {
120+
if mapk, err = d.DecodeInterface(); err != nil {
121+
return nil, err
122+
}
123+
if mapv, err = d.DecodeInterface(); err != nil {
124+
return nil, err
125+
}
126+
errorStack[i].Fields[mapk] = mapv
127+
}
128+
default:
129+
if err = d.Skip(); err != nil {
130+
return nil, err
131+
}
132+
}
133+
}
134+
135+
if i > 0 {
136+
errorStack[i-1].Prev = &errorStack[i]
137+
}
138+
}
139+
default:
140+
if err = d.Skip(); err != nil {
141+
return nil, err
142+
}
143+
}
144+
}
145+
146+
if len(errorStack) > 0 {
147+
return &errorStack[0], nil
148+
}
149+
150+
return nil, nil
151+
}

config.lua

+37
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ box.once("init", function()
130130
-- grants for sql tests
131131
box.schema.user.grant('test', 'create,read,write,drop,alter', 'space')
132132
box.schema.user.grant('test', 'create', 'sequence')
133+
134+
box.schema.user.create('no_grants')
133135
end)
134136

135137
local function func_name()
@@ -157,6 +159,41 @@ local function push_func(cnt)
157159
end
158160
rawset(_G, 'push_func', push_func)
159161

162+
local function tarantool_version_at_least(wanted_major, wanted_minor, wanted_patch)
163+
-- https://github.com/tarantool/crud/blob/733528be02c1ffa3dacc12c034ee58c9903127fc/test/helper.lua#L316-L337
164+
local major_minor_patch = _TARANTOOL:split('-', 1)[1]
165+
local major_minor_patch_parts = major_minor_patch:split('.', 2)
166+
167+
local major = tonumber(major_minor_patch_parts[1])
168+
local minor = tonumber(major_minor_patch_parts[2])
169+
local patch = tonumber(major_minor_patch_parts[3])
170+
171+
if major < (wanted_major or 0) then return false end
172+
if major > (wanted_major or 0) then return true end
173+
174+
if minor < (wanted_minor or 0) then return false end
175+
if minor > (wanted_minor or 0) then return true end
176+
177+
if patch < (wanted_patch or 0) then return false end
178+
if patch > (wanted_patch or 0) then return true end
179+
180+
return true
181+
end
182+
183+
if tarantool_version_at_least(2, 4, 1) then
184+
local e1 = box.error.new(box.error.UNKNOWN)
185+
local e2 = box.error.new(box.error.TIMEOUT)
186+
e2:set_prev(e1)
187+
rawset(_G, 'chained_error', e2)
188+
189+
local user = box.session.user()
190+
box.schema.func.create('forbidden_function', {body = 'function() end'})
191+
box.session.su('no_grants')
192+
local _, access_denied_error = pcall(function() box.func.forbidden_function:call() end)
193+
box.session.su(user)
194+
rawset(_G, 'access_denied_error', access_denied_error)
195+
end
196+
160197
box.space.test:truncate()
161198

162199
--box.schema.user.revoke('guest', 'read,write,execute', 'universe')

const.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ const (
3535
KeyExpression = 0x27
3636
KeyDefTuple = 0x28
3737
KeyData = 0x30
38-
KeyError24 = 0x31
38+
KeyError24 = 0x31 /* Error in pre-2.4 format */
3939
KeyMetaData = 0x32
4040
KeyBindCount = 0x34
4141
KeySQLText = 0x40
4242
KeySQLBind = 0x41
4343
KeySQLInfo = 0x42
4444
KeyStmtID = 0x43
45+
KeyError = 0x52 /* Extended error in >= 2.4 format. */
4546
KeyVersion = 0x54
4647
KeyFeatures = 0x55
4748
KeyTimeout = 0x56
@@ -56,6 +57,15 @@ const (
5657
KeySQLInfoRowCount = 0x00
5758
KeySQLInfoAutoincrementIds = 0x01
5859

60+
KeyErrorStack = 0x00
61+
KeyErrorType = 0x00
62+
KeyErrorFile = 0x01
63+
KeyErrorLine = 0x02
64+
KeyErrorMessage = 0x03
65+
KeyErrorErrno = 0x04
66+
KeyErrorErrcode = 0x05
67+
KeyErrorFields = 0x06
68+
5969
// https://github.com/fl00r/go-tarantool-1.6/issues/2
6070

6171
IterEq = uint32(0) // key == x ASC order

errors.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import "fmt"
44

55
// Error is wrapper around error returned by Tarantool.
66
type Error struct {
7-
Code uint32
8-
Msg string
7+
Code uint32
8+
Msg string
9+
ExtendedInfo *BoxError
910
}
1011

1112
// Error converts an Error to a string.
1213
func (tnterr Error) Error() string {
14+
if tnterr.ExtendedInfo != nil {
15+
return tnterr.ExtendedInfo.Error()
16+
}
17+
1318
return fmt.Sprintf("%s (0x%x)", tnterr.Msg, tnterr.Code)
1419
}
1520

response.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ func (resp *Response) decodeBody() (err error) {
151151
var stmtID, bindCount uint64
152152
var serverProtocolInfo ProtocolInfo
153153
var feature ProtocolFeature
154+
var errorExtendedInfo *BoxError = nil
154155

155156
d := newDecoder(&resp.buf)
156157

@@ -172,6 +173,10 @@ func (resp *Response) decodeBody() (err error) {
172173
if resp.Data, ok = res.([]interface{}); !ok {
173174
return fmt.Errorf("result is not array: %v", res)
174175
}
176+
case KeyError:
177+
if errorExtendedInfo, err = decodeBoxError(d); err != nil {
178+
return err
179+
}
175180
case KeyError24:
176181
if resp.Error, err = d.DecodeString(); err != nil {
177182
return err
@@ -236,7 +241,7 @@ func (resp *Response) decodeBody() (err error) {
236241

237242
if resp.Code != OkCode && resp.Code != PushCode {
238243
resp.Code &^= ErrorCodeBit
239-
err = Error{resp.Code, resp.Error}
244+
err = Error{resp.Code, resp.Error, errorExtendedInfo}
240245
}
241246
}
242247
return
@@ -247,6 +252,8 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
247252
offset := resp.buf.Offset()
248253
defer resp.buf.Seek(offset)
249254

255+
var errorExtendedInfo *BoxError = nil
256+
250257
var l int
251258
d := newDecoder(&resp.buf)
252259
if l, err = d.DecodeMapLen(); err != nil {
@@ -262,6 +269,10 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
262269
if err = d.Decode(res); err != nil {
263270
return err
264271
}
272+
case KeyError:
273+
if errorExtendedInfo, err = decodeBoxError(d); err != nil {
274+
return err
275+
}
265276
case KeyError24:
266277
if resp.Error, err = d.DecodeString(); err != nil {
267278
return err
@@ -282,7 +293,7 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
282293
}
283294
if resp.Code != OkCode && resp.Code != PushCode {
284295
resp.Code &^= ErrorCodeBit
285-
err = Error{resp.Code, resp.Error}
296+
err = Error{resp.Code, resp.Error, errorExtendedInfo}
286297
}
287298
}
288299
return

0 commit comments

Comments
 (0)