Skip to content

Commit

Permalink
api: support error type in MessagePack
Browse files Browse the repository at this point in the history
Tarantool supports error extension type since version 2.4.1 [1],
encoding was introduced in Tarantool 2.10.0 [2]. This patch introduces
the support of Tarantool error extension type in msgpack decoders and
encoders.

Tarantool error extension type objects are decoded to
`*tarantool.BoxError` type. `*tarantool.BoxError` may be encoded to
Tarantool error extension type objects.

Error extension type internals are the same as errors extended
information: the only difference is that extra information is encoded as
a separate error dictionary field and error extension type objects are
encoded as MessagePack extension type objects.

The only way to receive an error extension type object from Tarantool is
to receive an explicitly built `box.error` object: either from
`return box.error.new(...)` or a tuple with it. All errors raised within
Tarantool (including those raised with `box.error(...)`) are encoded
based on the same rules as simple errors due to backward compatibility.

It is possible to create error extension type objects with Go code,
but it not likely to be really useful since most of their fields is
computed on error initialization on the server side (even for custom
error types).

This patch also adds ErrorExtensionFeature flag to client protocol
features list. Without this flag, all `box.error` object sent over
iproto are encoded to string. We behave like Tarantool `net.box` here:
if we support the feature, we provide the feature flag.

Since it may become too complicated to enable/disable feature flag
through import, error extension type is available as a part of the base
package, in contrary to Decimal, UUID, Datetime and Interval types which
are enabled by importing underscore subpackage.

1. tarantool/tarantool#4398
2. tarantool/tarantool#6433

Closes #209
  • Loading branch information
DifferentialOrange committed Nov 23, 2022
1 parent 0d431a6 commit c5817cf
Show file tree
Hide file tree
Showing 12 changed files with 578 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

- Support iproto feature discovery (#120).
- Support errors extended information (#209).
- Error type support in MessagePack (#209).

### Changed

Expand Down
127 changes: 125 additions & 2 deletions box_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
)

const errorExtID = 3

// 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/
Expand Down Expand Up @@ -38,8 +40,6 @@ func (boxError *BoxError) Error() string {
}

// Depth computes the count of errors in stack, including the current one.
//
// Since 1.10.0
func (boxError *BoxError) Depth() (depth int) {
cur := boxError

Expand Down Expand Up @@ -149,3 +149,126 @@ func decodeBoxError(d *decoder) (*BoxError, error) {

return nil, nil
}

func encodeBoxError(enc *encoder, boxError *BoxError) (err error) {
if err = enc.EncodeMapLen(1); err != nil {
return err
}
if err = encodeUint(enc, KeyErrorStack); err != nil {
return err
}

var stackDepth = boxError.Depth()
if err = enc.EncodeArrayLen(stackDepth); err != nil {
return err
}

for ; stackDepth > 0; stackDepth-- {
fieldsLen := len(boxError.Fields)

if fieldsLen > 0 {
if err = enc.EncodeMapLen(7); err != nil {
return err
}
} else {
if err = enc.EncodeMapLen(6); err != nil {
return err
}
}

if err = encodeUint(enc, KeyErrorType); err != nil {
return err
}
if err = enc.EncodeString(boxError.Type); err != nil {
return err
}

if err = encodeUint(enc, KeyErrorFile); err != nil {
return err
}
if err = enc.EncodeString(boxError.File); err != nil {
return err
}

if err = encodeUint(enc, KeyErrorLine); err != nil {
return err
}
if err = enc.EncodeUint32(boxError.Line); err != nil {
return err
}

if err = encodeUint(enc, KeyErrorMessage); err != nil {
return err
}
if err = enc.EncodeString(boxError.Msg); err != nil {
return err
}

if err = encodeUint(enc, KeyErrorErrno); err != nil {
return err
}
if err = enc.EncodeUint32(boxError.Errno); err != nil {
return err
}

if err = encodeUint(enc, KeyErrorErrcode); err != nil {
return err
}
if err = enc.EncodeUint32(boxError.Code); err != nil {
return err
}

if fieldsLen > 0 {
if err = encodeUint(enc, KeyErrorFields); err != nil {
return err
}

if err = enc.EncodeMapLen(fieldsLen); err != nil {
return err
}

for k, v := range boxError.Fields {
if err = enc.Encode(k); err != nil {
return err
}
if err = enc.Encode(v); err != nil {
return err
}
}
}

if stackDepth > 1 {
boxError = boxError.Prev
}
}

return nil
}

// MarshalMsgpack serializes the BoxError into a MessagePack representation.
func (boxError *BoxError) MarshalMsgpack() (b []byte, err error) {
var buf smallWBuf

enc := newEncoder(&buf)
if err = encodeBoxError(enc, boxError); err != nil {
return b, err
}

return buf.b, nil
}

// UnmarshalMsgpack deserializes a BoxError value from a MessagePack
// representation.
func (boxError *BoxError) UnmarshalMsgpack(b []byte) error {
var buf smallBuf = smallBuf{b: b}

dec := newDecoder(&buf)
val, err := decodeBoxError(dec)
if err != nil {
return err
}

*boxError = *val

return nil
}
Loading

0 comments on commit c5817cf

Please sign in to comment.