From 5cd753441506f5b4246521a76b61236d38287a4b Mon Sep 17 00:00:00 2001 From: Albert Skalt Date: Mon, 21 Aug 2023 17:19:36 +0300 Subject: [PATCH] crud: support `operation_data` field in errors 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 --- CHANGELOG.md | 1 + crud/error.go | 36 +++++++++++++++++--- crud/example_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ crud/result.go | 14 +++----- 4 files changed, 118 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ad9faa38..211ea25a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crud/error.go b/crud/error.go index b0b267fbe..9233de5c3 100644 --- a/crud/error.go +++ b/crud/error.go @@ -1,6 +1,7 @@ package crud import ( + "reflect" "strings" "github.com/vmihailenco/msgpack/v5" @@ -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. @@ -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 @@ -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. @@ -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 diff --git a/crud/example_test.go b/crud/example_test.go index 155bb175d..a6c6577bd 100644 --- a/crud/example_test.go +++ b/crud/example_test.go @@ -78,6 +78,86 @@ func ExampleResult_rowsCustomType() { // [{{} 2010 45 bla}] } +func ExampleResult_operationData() { + conn := exampleConnect() + req := crud.MakeInsertObjectManyRequest(exampleSpace).Objects([]crud.Object{ + crud.MapObject{ + "id": 2, + "bucket_id": 3, + "name": "Makar", + }, + crud.MapObject{ + "id": 2, + "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: + // [2 3 Vasya] + // map[bucket_id:5 id:3] +} + +func ExampleResult_operationDataCustomType() { + 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, + }, + }) + + type Tuple struct { + Id uint64 `msgpack:"id,omitempty"` + BucketId uint64 `msgpack:"bucket_id,omitempty"` + Name string `msgpack:"name,omitempty"` + } + + ret := crud.MakeResult(reflect.TypeOf(Tuple{})) + if err := conn.Do(req).GetTyped(&ret); err != nil { + crudErrs := err.(crud.ErrorMany) + 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: + // Erroneous data: + // {1 3 Vasya} + // {3 5 } +} + // ExampleResult_many demonstrates that there is no difference in a // response from *ManyRequest. func ExampleResult_many() { diff --git a/crud/result.go b/crud/result.go index b594e5de7..5bce19d88 100644 --- a/crud/result.go +++ b/crud/result.go @@ -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++ {