Skip to content

Commit

Permalink
crud: support operation_data field in errors
Browse files Browse the repository at this point in the history
This patch adds `operation_data` decoding for the `crud.Error`.

The `operation_data` type is determined as `rowType` in `crud.Result`.

Also, according to [1], an error can contain one of the following:
- an error
- an array of errors
- nil

So the error decoding logic has been modified to consider each case,
in order to avoid comparing an error to nil.

1. https://github.com/tarantool/crud/tree/master#api

Closes #330
  • Loading branch information
askalt committed Aug 21, 2023
1 parent b17735b commit 45514fc
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
- More linters on CI (#310)
- Meaningful description for read/write socket errors (#129)
- Support password and password file to decrypt private SSL key file (#319)
- Support `operation_data` in `crud.Error` (#330)

### Changed

Expand Down
36 changes: 32 additions & 4 deletions crud/error.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package crud

import (
"reflect"
"strings"

"github.com/vmihailenco/msgpack/v5"
Expand All @@ -21,6 +22,15 @@ type Error struct {
Stack string
// Str is the text of reason with error class.
Str string
// OperationData is the object/tuple with which an error occurred.
OperationData interface{}
// operationDataType contains the type of OperationData.
operationDataType reflect.Type
}

// newError creates an Error object with a custom operation data type to decoding.
func newError(operationDataType reflect.Type) *Error {
return &Error{operationDataType: operationDataType}
}

// DecodeMsgpack provides custom msgpack decoder.
Expand Down Expand Up @@ -59,6 +69,18 @@ func (e *Error) DecodeMsgpack(d *msgpack.Decoder) error {
if e.Str, err = d.DecodeString(); err != nil {
return err
}
case "operation_data":
if e.operationDataType != nil {
tuple := reflect.New(e.operationDataType)
if err = d.DecodeValue(tuple); err != nil {
return err
}
e.OperationData = tuple.Elem().Interface()
} else {
if err = d.Decode(&e.OperationData); err != nil {
return err
}
}
default:
if err := d.Skip(); err != nil {
return err
Expand All @@ -77,6 +99,13 @@ func (e Error) Error() string {
// ErrorMany describes CRUD error object for `_many` methods.
type ErrorMany struct {
Errors []Error
// operationDataType contains the type of OperationData for each Error.
operationDataType reflect.Type
}

// newErrorMany creates an ErrorMany object with a custom operation data type to decoding.
func newErrorMany(operationDataType reflect.Type) *ErrorMany {
return &ErrorMany{operationDataType: operationDataType}
}

// DecodeMsgpack provides custom msgpack decoder.
Expand All @@ -88,16 +117,15 @@ func (e *ErrorMany) DecodeMsgpack(d *msgpack.Decoder) error {

var errs []Error
for i := 0; i < l; i++ {
var crudErr *Error = nil
crudErr := newError(e.operationDataType)
if err := d.Decode(&crudErr); err != nil {
return err
} else if crudErr != nil {
errs = append(errs, *crudErr)
}
errs = append(errs, *crudErr)
}

if len(errs) > 0 {
*e = ErrorMany{Errors: errs}
e.Errors = errs
}

return nil
Expand Down
74 changes: 74 additions & 0 deletions crud/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,80 @@ func ExampleResult_rowsCustomType() {
// [{{} 2010 45 bla}]
}

func ExampleResult_operationData() {
conn := exampleConnect()
req := crud.MakeInsertObjectManyRequest(exampleSpace).Objects([]crud.Object{
crud.MapObject{
"id": 1,
"bucket_id": 3,
"name": "Makar",
},
crud.MapObject{
"id": 1,
"bucket_id": 3,
"name": "Vasya",
},
crud.MapObject{
"id": 3,
"bucket_id": 5,
},
})

ret := crud.Result{}
if err := conn.Do(req).GetTyped(&ret); err != nil {
crudErrs := err.(crud.ErrorMany)
fmt.Println("Erroneous data:")
for _, crudErr := range crudErrs.Errors {
fmt.Println(crudErr.OperationData)
}
} else {
fmt.Println(ret.Metadata)
fmt.Println(ret.Rows)
}

// Output:
// Erroneous data:
// [1 3 Vasya]
// map[bucket_id:5 id:3]
}

func ExampleResult_operationDataCustomType() {
conn := exampleConnect()
req := crud.MakeInsertManyRequest(exampleSpace).Tuples([]interface{}{
[]interface{}{uint(1), 1, "bla"},
[]interface{}{uint(1), 2, "blabla"},
[]interface{}{uint(2011), 3, nil},
})

type Tuple struct {
_msgpack struct{} `msgpack:",asArray"` //nolint: structcheck,unused
Id uint64
BucketId uint64
Name string
}

ret := crud.MakeResult(reflect.TypeOf(Tuple{}))
if err := conn.Do(req).GetTyped(&ret); err != nil {
crudErrs := err.(crud.ErrorMany)
// We need to trim the error message to make the example repeatable.
errmsg := crudErrs.Error()[:10]
fmt.Printf("Failed to execute request: %s\n", errmsg)
fmt.Println("Erroneous data:")
for _, crudErr := range crudErrs.Errors {
operationData := crudErr.OperationData.(Tuple)
fmt.Println(operationData)
}
} else {
fmt.Println(ret.Metadata)
fmt.Println(ret.Rows)
}
// Output:
// Failed to execute request: CallError:
// Erroneous data:
// {{} 1 2 blabla}
// {{} 2011 3 }
}

// ExampleResult_many demonstrates that there is no difference in a
// response from *ManyRequest.
func ExampleResult_many() {
Expand Down
14 changes: 5 additions & 9 deletions crud/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,17 @@ func (r *Result) DecodeMsgpack(d *msgpack.Decoder) error {

var retErr error
if msgpackIsArray(code) {
var crudErr *ErrorMany
crudErr := newErrorMany(r.rowType)
if err := d.Decode(&crudErr); err != nil {
return err
}
if crudErr != nil {
retErr = *crudErr
}
} else {
var crudErr *Error
retErr = *crudErr
} else if code != msgpcode.Nil {
crudErr := newError(r.rowType)
if err := d.Decode(&crudErr); err != nil {
return err
}
if crudErr != nil {
retErr = *crudErr
}
retErr = *crudErr
}

for i := 2; i < arrLen; i++ {
Expand Down

0 comments on commit 45514fc

Please sign in to comment.