Skip to content

Commit

Permalink
api: support errors extra information
Browse files Browse the repository at this point in the history
Since Tarantool 2.4.1, iproto error responses contain extra info with
backtrace [1]. After this patch, Error would contain ExtraInfo field
(BoxError object), if it was provided.

1. https://www.tarantool.io/en/doc/latest/dev_guide/internals/box_protocol/#responses-for-errors

Part of #209
  • Loading branch information
DifferentialOrange committed Nov 21, 2022
1 parent 7592b93 commit a3bc8c4
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
### Added

- Support iproto feature discovery (#120).
- Support errors extra information (#209).

### Changed

Expand Down
220 changes: 220 additions & 0 deletions box_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package tarantool

// BoxError is a type representing Tarantool `box.error` object: a single
// MP_ERROR_STACK object with a link to the previous stack error.
type BoxError struct {
Type string // Type that implies source, for example "ClientError".
File string // Source code file where error was caught.
Line int64 // Line number in source code file.
Message string // Text of reason.
Errno int64 // Ordinal number of the error.
Errcode int64 // Number of the error as defined in `errcode.h`.
// Additional fields depending on error type. For example, if
// type is "AccessDeniedError", then it will include "object_type",
// "object_name", "access_type".
Fields map[interface{}]interface{}
Prev *BoxError // Previous error in stack.
}

func (err BoxError) Depth() (depth int) {
depth = 1
for err.Prev != nil {
err = *err.Prev
depth++
}
return depth
}

func decodeBoxErrorStack(d *decoder) (*BoxError, error) {
var l, larr, l1, l2 int
var errorStack []BoxError
var err error
var mapk, mapv interface{}

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.DecodeInt64(); err != nil {
return nil, err
}
case KeyErrorMessage:
if errorStack[i].Message, err = d.DecodeString(); err != nil {
return nil, err
}
case KeyErrorErrno:
if errorStack[i].Errno, err = d.DecodeInt64(); err != nil {
return nil, err
}
case KeyErrorErrcode:
if errorStack[i].Errcode, err = d.DecodeInt64(); err != nil {
return nil, err
}
case KeyErrorFields:
errorStack[i].Fields = make(map[interface{}]interface{})
if l2, err = d.DecodeMapLen(); err != nil {
return nil, err
}
for ; l2 > 0; l2-- {
if mapk, err = d.DecodeInterface(); 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
}

// func encodeBoxError(enc *encoder, err BoxError) (*BoxError, error) {
// var l, larr, l1, l2 int
// var errorStack []BoxError
// var err error
// var mapk, mapv interface{}

// 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.DecodeInt64(); err != nil {
// return nil, err
// }
// case KeyErrorMessage:
// if errorStack[i].Message, err = d.DecodeString(); err != nil {
// return nil, err
// }
// case KeyErrorErrno:
// if errorStack[i].Errno, err = d.DecodeInt64(); err != nil {
// return nil, err
// }
// case KeyErrorErrcode:
// if errorStack[i].Errcode, err = d.DecodeInt64(); err != nil {
// return nil, err
// }
// case KeyErrorFields:
// errorStack[i].Fields = make(map[interface{}]interface{})
// if l2, err = d.DecodeMapLen(); err != nil {
// return nil, err
// }
// for ; l2 > 0; l2-- {
// if mapk, err = d.DecodeInterface(); 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
// }
12 changes: 11 additions & 1 deletion const.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ const (
KeyExpression = 0x27
KeyDefTuple = 0x28
KeyData = 0x30
KeyError24 = 0x31
KeyError24 = 0x31 /* Error in pre-2.4 format */
KeyMetaData = 0x32
KeyBindCount = 0x34
KeySQLText = 0x40
KeySQLBind = 0x41
KeySQLInfo = 0x42
KeyStmtID = 0x43
KeyError = 0x52 /* Extended error in >= 2.4 format. */
KeyVersion = 0x54
KeyFeatures = 0x55
KeyTimeout = 0x56
Expand All @@ -56,6 +57,15 @@ const (
KeySQLInfoRowCount = 0x00
KeySQLInfoAutoincrementIds = 0x01

KeyErrorStack = 0x00
KeyErrorType = 0x00
KeyErrorFile = 0x01
KeyErrorLine = 0x02
KeyErrorMessage = 0x03
KeyErrorErrno = 0x04
KeyErrorErrcode = 0x05
KeyErrorFields = 0x06

// https://github.com/fl00r/go-tarantool-1.6/issues/2

IterEq = uint32(0) // key == x ASC order
Expand Down
5 changes: 3 additions & 2 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import "fmt"

// Error is wrapper around error returned by Tarantool.
type Error struct {
Code uint32
Msg string
Code uint32
Msg string
ExtraInfo *BoxError
}

// Error converts an Error to a string.
Expand Down
15 changes: 13 additions & 2 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ func (resp *Response) decodeBody() (err error) {
var stmtID, bindCount uint64
var serverProtocolInfo ProtocolInfo
var feature ProtocolFeature
var extraErrorInfo *BoxError = nil

d := newDecoder(&resp.buf)

Expand All @@ -176,6 +177,10 @@ func (resp *Response) decodeBody() (err error) {
if resp.Error, err = d.DecodeString(); err != nil {
return err
}
case KeyError:
if extraErrorInfo, err = decodeBoxErrorStack(d); err != nil {
return err
}
case KeySQLInfo:
if err = d.Decode(&resp.SQLInfo); err != nil {
return err
Expand Down Expand Up @@ -236,7 +241,7 @@ func (resp *Response) decodeBody() (err error) {

if resp.Code != OkCode && resp.Code != PushCode {
resp.Code &^= ErrorCodeBit
err = Error{resp.Code, resp.Error}
err = Error{resp.Code, resp.Error, extraErrorInfo}
}
}
return
Expand All @@ -248,6 +253,8 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
defer resp.buf.Seek(offset)

var l int
var extraErrorInfo *BoxError = nil

d := newDecoder(&resp.buf)
if l, err = d.DecodeMapLen(); err != nil {
return err
Expand All @@ -266,6 +273,10 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
if resp.Error, err = d.DecodeString(); err != nil {
return err
}
case KeyError:
if extraErrorInfo, err = decodeBoxErrorStack(d); err != nil {
return err
}
case KeySQLInfo:
if err = d.Decode(&resp.SQLInfo); err != nil {
return err
Expand All @@ -282,7 +293,7 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) {
}
if resp.Code != OkCode && resp.Code != PushCode {
resp.Code &^= ErrorCodeBit
err = Error{resp.Code, resp.Error}
err = Error{resp.Code, resp.Error, extraErrorInfo}
}
}
return
Expand Down
Loading

0 comments on commit a3bc8c4

Please sign in to comment.