forked from fl00r/go-tarantool-1.6
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
500da07
commit 4e3bdfa
Showing
9 changed files
with
443 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package tarantool | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// BoxError is a type representing Tarantool `box.error` object: a single | ||
// MP_ERROR_STACK object with a link to the previous stack error. | ||
// See https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_error/error/ | ||
// | ||
// Since 1.10.0 | ||
type BoxError struct { | ||
// Type is error type that implies its source (for example, "ClientError"). | ||
Type string | ||
// File is a source code file where the error was caught. | ||
File string | ||
// Line is a number of line in the source code file where the error was caught. | ||
Line uint64 | ||
// Msg is the text of reason. | ||
Msg string | ||
// Errno is the ordinal number of the error. | ||
Errno uint64 | ||
// Code is the number of the error as defined in `errcode.h`. | ||
Code uint64 | ||
// Fields are additional fields depending on error type. For example, if | ||
// type is "AccessDeniedError", then it will include "object_type", | ||
// "object_name", "access_type". | ||
Fields map[string]interface{} | ||
// Prev is the previous error in stack. | ||
Prev *BoxError | ||
} | ||
|
||
// Error converts a BoxError to a string. | ||
func (e *BoxError) Error() string { | ||
s := fmt.Sprintf("%s (%s, code 0x%x), see %s line %d", | ||
e.Msg, e.Type, e.Code, e.File, e.Line) | ||
|
||
if e.Prev != nil { | ||
return fmt.Sprintf("%s: %s", s, e.Prev) | ||
} | ||
|
||
return s | ||
} | ||
|
||
// Depth computes the count of errors in stack, including the current one. | ||
func (e *BoxError) Depth() int { | ||
depth := int(0) | ||
|
||
cur := e | ||
for cur != nil { | ||
cur = cur.Prev | ||
depth++ | ||
} | ||
|
||
return depth | ||
} | ||
|
||
func decodeBoxError(d *decoder) (*BoxError, error) { | ||
var l, larr, l1, l2 int | ||
var errorStack []BoxError | ||
var err error | ||
|
||
if l, err = d.DecodeMapLen(); err != nil { | ||
return nil, err | ||
} | ||
|
||
for ; l > 0; l-- { | ||
var cd int | ||
if cd, err = d.DecodeInt(); err != nil { | ||
return nil, err | ||
} | ||
switch cd { | ||
case KeyErrorStack: | ||
if larr, err = d.DecodeArrayLen(); err != nil { | ||
return nil, err | ||
} | ||
|
||
errorStack = make([]BoxError, larr) | ||
|
||
for i := 0; i < larr; i++ { | ||
if l1, err = d.DecodeMapLen(); err != nil { | ||
return nil, err | ||
} | ||
|
||
for ; l1 > 0; l1-- { | ||
var cd1 int | ||
if cd1, err = d.DecodeInt(); err != nil { | ||
return nil, err | ||
} | ||
switch cd1 { | ||
case KeyErrorType: | ||
if errorStack[i].Type, err = d.DecodeString(); err != nil { | ||
return nil, err | ||
} | ||
case KeyErrorFile: | ||
if errorStack[i].File, err = d.DecodeString(); err != nil { | ||
return nil, err | ||
} | ||
case KeyErrorLine: | ||
if errorStack[i].Line, err = d.DecodeUint64(); err != nil { | ||
return nil, err | ||
} | ||
case KeyErrorMessage: | ||
if errorStack[i].Msg, err = d.DecodeString(); err != nil { | ||
return nil, err | ||
} | ||
case KeyErrorErrno: | ||
if errorStack[i].Errno, err = d.DecodeUint64(); err != nil { | ||
return nil, err | ||
} | ||
case KeyErrorErrcode: | ||
if errorStack[i].Code, err = d.DecodeUint64(); err != nil { | ||
return nil, err | ||
} | ||
case KeyErrorFields: | ||
var mapk string | ||
var mapv interface{} | ||
|
||
errorStack[i].Fields = make(map[string]interface{}) | ||
|
||
if l2, err = d.DecodeMapLen(); err != nil { | ||
return nil, err | ||
} | ||
for ; l2 > 0; l2-- { | ||
if mapk, err = d.DecodeString(); err != nil { | ||
return nil, err | ||
} | ||
if mapv, err = d.DecodeInterface(); err != nil { | ||
return nil, err | ||
} | ||
errorStack[i].Fields[mapk] = mapv | ||
} | ||
default: | ||
if err = d.Skip(); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
if i > 0 { | ||
errorStack[i-1].Prev = &errorStack[i] | ||
} | ||
} | ||
default: | ||
if err = d.Skip(); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
if len(errorStack) > 0 { | ||
return &errorStack[0], nil | ||
} | ||
|
||
return nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package tarantool_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
. "github.com/tarantool/go-tarantool" | ||
) | ||
|
||
var samples = map[string]BoxError{ | ||
"SimpleError": { | ||
Type: "ClientError", | ||
File: "config.lua", | ||
Line: uint64(202), | ||
Msg: "Unknown error", | ||
Errno: uint64(0), | ||
Code: uint64(0), | ||
}, | ||
"AccessDeniedError": { | ||
Type: "AccessDeniedError", | ||
File: "/__w/sdk/sdk/tarantool-2.10/tarantool/src/box/func.c", | ||
Line: uint64(535), | ||
Msg: "Execute access to function 'forbidden_function' is denied for user 'no_grants'", | ||
Errno: uint64(0), | ||
Code: uint64(42), | ||
Fields: map[string]interface{}{ | ||
"object_type": "function", | ||
"object_name": "forbidden_function", | ||
"access_type": "Execute", | ||
}, | ||
}, | ||
"ChainedError": { | ||
Type: "ClientError", | ||
File: "config.lua", | ||
Line: uint64(205), | ||
Msg: "Timeout exceeded", | ||
Errno: uint64(0), | ||
Code: uint64(78), | ||
Prev: &BoxError{ | ||
Type: "ClientError", | ||
File: "config.lua", | ||
Line: uint64(202), | ||
Msg: "Unknown error", | ||
Errno: uint64(0), | ||
Code: uint64(0), | ||
}, | ||
}, | ||
} | ||
|
||
var stringCases = map[string]struct { | ||
e BoxError | ||
s string | ||
}{ | ||
"SimpleError": { | ||
samples["SimpleError"], | ||
"Unknown error (ClientError, code 0x0), see config.lua line 202", | ||
}, | ||
"AccessDeniedError": { | ||
samples["AccessDeniedError"], | ||
"Execute access to function 'forbidden_function' is denied for user " + | ||
"'no_grants' (AccessDeniedError, code 0x2a), see " + | ||
"/__w/sdk/sdk/tarantool-2.10/tarantool/src/box/func.c line 535", | ||
}, | ||
"ChainedError": { | ||
samples["ChainedError"], | ||
"Timeout exceeded (ClientError, code 0x4e), see config.lua line 205: " + | ||
"Unknown error (ClientError, code 0x0), see config.lua line 202", | ||
}, | ||
} | ||
|
||
func TestBoxErrorStringRepr(t *testing.T) { | ||
for name, testcase := range stringCases { | ||
t.Run(name, func(t *testing.T) { | ||
require.Equal(t, testcase.s, testcase.e.Error()) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.