Skip to content

api: support errors extended information and error type in MessagePack #229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

### Added

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

### Changed

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

import (
"bytes"
"fmt"
)

const errorExtID = 3

const (
keyErrorStack = 0x00
keyErrorType = 0x00
keyErrorFile = 0x01
keyErrorLine = 0x02
keyErrorMessage = 0x03
keyErrorErrno = 0x04
keyErrorErrcode = 0x05
keyErrorFields = 0x06
)

// 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 nil, fmt.Errorf("msgpack: unexpected empty BoxError stack on decode")
}

return &errorStack[0], nil
}

func encodeBoxError(enc *encoder, boxError *BoxError) error {
if boxError == nil {
return fmt.Errorf("msgpack: unexpected nil BoxError on encode")
}

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.EncodeUint64(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.EncodeUint64(boxError.Errno); err != nil {
return err
}

if err := encodeUint(enc, keyErrorErrcode); err != nil {
return err
}
if err := enc.EncodeUint64(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.EncodeString(k); err != nil {
return err
}
if err := enc.Encode(v); err != nil {
return err
}
}
}

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

return nil
}

// UnmarshalMsgpack deserializes a BoxError value from a MessagePack
// representation.
func (e *BoxError) UnmarshalMsgpack(b []byte) error {
if e == nil {
panic("cannot unmarshal to a nil pointer")
}

buf := bytes.NewBuffer(b)
dec := newDecoder(buf)

if val, err := decodeBoxError(dec); err != nil {
return err
} else {
*e = *val
return nil
}
}

// MarshalMsgpack serializes the BoxError into a MessagePack representation.
func (e *BoxError) MarshalMsgpack() ([]byte, error) {
var buf bytes.Buffer

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

return buf.Bytes(), nil
}
Loading