Skip to content

Commit d8df65d

Browse files
askaltoleg-jukovec
authored andcommitted
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
1 parent b17735b commit d8df65d

File tree

5 files changed

+160
-12
lines changed

5 files changed

+160
-12
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1616
- More linters on CI (#310)
1717
- Meaningful description for read/write socket errors (#129)
1818
- Support password and password file to decrypt private SSL key file (#319)
19+
- Support `operation_data` in `crud.Error` (#330)
1920

2021
### Changed
2122

crud/error.go

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package crud
22

33
import (
4+
"reflect"
45
"strings"
56

67
"github.com/vmihailenco/msgpack/v5"
@@ -21,6 +22,15 @@ type Error struct {
2122
Stack string
2223
// Str is the text of reason with error class.
2324
Str string
25+
// OperationData is the object/tuple with which an error occurred.
26+
OperationData interface{}
27+
// operationDataType contains the type of OperationData.
28+
operationDataType reflect.Type
29+
}
30+
31+
// newError creates an Error object with a custom operation data type to decoding.
32+
func newError(operationDataType reflect.Type) *Error {
33+
return &Error{operationDataType: operationDataType}
2434
}
2535

2636
// DecodeMsgpack provides custom msgpack decoder.
@@ -59,6 +69,18 @@ func (e *Error) DecodeMsgpack(d *msgpack.Decoder) error {
5969
if e.Str, err = d.DecodeString(); err != nil {
6070
return err
6171
}
72+
case "operation_data":
73+
if e.operationDataType != nil {
74+
tuple := reflect.New(e.operationDataType)
75+
if err = d.DecodeValue(tuple); err != nil {
76+
return err
77+
}
78+
e.OperationData = tuple.Elem().Interface()
79+
} else {
80+
if err = d.Decode(&e.OperationData); err != nil {
81+
return err
82+
}
83+
}
6284
default:
6385
if err := d.Skip(); err != nil {
6486
return err
@@ -77,6 +99,13 @@ func (e Error) Error() string {
7799
// ErrorMany describes CRUD error object for `_many` methods.
78100
type ErrorMany struct {
79101
Errors []Error
102+
// operationDataType contains the type of OperationData for each Error.
103+
operationDataType reflect.Type
104+
}
105+
106+
// newErrorMany creates an ErrorMany object with a custom operation data type to decoding.
107+
func newErrorMany(operationDataType reflect.Type) *ErrorMany {
108+
return &ErrorMany{operationDataType: operationDataType}
80109
}
81110

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

89118
var errs []Error
90119
for i := 0; i < l; i++ {
91-
var crudErr *Error = nil
120+
crudErr := newError(e.operationDataType)
92121
if err := d.Decode(&crudErr); err != nil {
93122
return err
94-
} else if crudErr != nil {
95-
errs = append(errs, *crudErr)
96123
}
124+
errs = append(errs, *crudErr)
97125
}
98126

99127
if len(errs) > 0 {
100-
*e = ErrorMany{Errors: errs}
128+
e.Errors = errs
101129
}
102130

103131
return nil

crud/example_test.go

+86
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,92 @@ func ExampleResult_rowsCustomType() {
7878
// [{{} 2010 45 bla}]
7979
}
8080

81+
// ExampleResult_operationData demonstrates how to obtain information
82+
// about erroneous objects from crud.Error using `OperationData` field.
83+
func ExampleResult_operationData() {
84+
conn := exampleConnect()
85+
req := crud.MakeInsertObjectManyRequest(exampleSpace).Objects([]crud.Object{
86+
crud.MapObject{
87+
"id": 2,
88+
"bucket_id": 3,
89+
"name": "Makar",
90+
},
91+
crud.MapObject{
92+
"id": 2,
93+
"bucket_id": 3,
94+
"name": "Vasya",
95+
},
96+
crud.MapObject{
97+
"id": 3,
98+
"bucket_id": 5,
99+
},
100+
})
101+
102+
ret := crud.Result{}
103+
if err := conn.Do(req).GetTyped(&ret); err != nil {
104+
crudErrs := err.(crud.ErrorMany)
105+
fmt.Println("Erroneous data:")
106+
for _, crudErr := range crudErrs.Errors {
107+
fmt.Println(crudErr.OperationData)
108+
}
109+
} else {
110+
fmt.Println(ret.Metadata)
111+
fmt.Println(ret.Rows)
112+
}
113+
114+
// Output:
115+
// Erroneous data:
116+
// [2 3 Vasya]
117+
// map[bucket_id:5 id:3]
118+
}
119+
120+
// ExampleResult_operationDataCustomType demonstrates the ability
121+
// to cast `OperationData` field, extracted from a CRUD error during decoding
122+
// using crud.Result, to a custom type.
123+
// The type of `OperationData` is determined as the crud.Result row type.
124+
func ExampleResult_operationDataCustomType() {
125+
conn := exampleConnect()
126+
req := crud.MakeInsertObjectManyRequest(exampleSpace).Objects([]crud.Object{
127+
crud.MapObject{
128+
"id": 1,
129+
"bucket_id": 3,
130+
"name": "Makar",
131+
},
132+
crud.MapObject{
133+
"id": 1,
134+
"bucket_id": 3,
135+
"name": "Vasya",
136+
},
137+
crud.MapObject{
138+
"id": 3,
139+
"bucket_id": 5,
140+
},
141+
})
142+
143+
type Tuple struct {
144+
Id uint64 `msgpack:"id,omitempty"`
145+
BucketId uint64 `msgpack:"bucket_id,omitempty"`
146+
Name string `msgpack:"name,omitempty"`
147+
}
148+
149+
ret := crud.MakeResult(reflect.TypeOf(Tuple{}))
150+
if err := conn.Do(req).GetTyped(&ret); err != nil {
151+
crudErrs := err.(crud.ErrorMany)
152+
fmt.Println("Erroneous data:")
153+
for _, crudErr := range crudErrs.Errors {
154+
operationData := crudErr.OperationData.(Tuple)
155+
fmt.Println(operationData)
156+
}
157+
} else {
158+
fmt.Println(ret.Metadata)
159+
fmt.Println(ret.Rows)
160+
}
161+
// Output:
162+
// Erroneous data:
163+
// {1 3 Vasya}
164+
// {3 5 }
165+
}
166+
81167
// ExampleResult_many demonstrates that there is no difference in a
82168
// response from *ManyRequest.
83169
func ExampleResult_many() {

crud/result.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -137,20 +137,20 @@ func (r *Result) DecodeMsgpack(d *msgpack.Decoder) error {
137137

138138
var retErr error
139139
if msgpackIsArray(code) {
140-
var crudErr *ErrorMany
140+
crudErr := newErrorMany(r.rowType)
141141
if err := d.Decode(&crudErr); err != nil {
142142
return err
143143
}
144-
if crudErr != nil {
145-
retErr = *crudErr
146-
}
147-
} else {
148-
var crudErr *Error
144+
retErr = *crudErr
145+
} else if code != msgpcode.Nil {
146+
crudErr := newError(r.rowType)
149147
if err := d.Decode(&crudErr); err != nil {
150148
return err
151149
}
152-
if crudErr != nil {
153-
retErr = *crudErr
150+
retErr = *crudErr
151+
} else {
152+
if err := d.DecodeNil(); err != nil {
153+
return err
154154
}
155155
}
156156

crud/result_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package crud_test
2+
3+
import (
4+
"bytes"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
"github.com/tarantool/go-tarantool/v2/crud"
10+
"github.com/vmihailenco/msgpack/v5"
11+
)
12+
13+
func TestResult_DecodeMsgpack(t *testing.T) {
14+
sampleCrudResponse := []interface{}{
15+
map[string]interface{}{
16+
"rows": []interface{}{"1", "2", "3"},
17+
},
18+
nil,
19+
}
20+
responses := []interface{}{sampleCrudResponse, sampleCrudResponse}
21+
22+
b := bytes.NewBuffer([]byte{})
23+
enc := msgpack.NewEncoder(b)
24+
err := enc.Encode(responses)
25+
require.NoError(t, err)
26+
27+
var results []crud.Result
28+
decoder := msgpack.NewDecoder(b)
29+
err = decoder.DecodeValue(reflect.ValueOf(&results))
30+
require.NoError(t, err)
31+
require.Equal(t, results[0].Rows, []interface{}{"1", "2", "3"})
32+
require.Equal(t, results[1].Rows, []interface{}{"1", "2", "3"})
33+
}

0 commit comments

Comments
 (0)