-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- `Mapper[T]`: this in an interface for converting from a certain fixed type to any. Some basic mappers were implemented: parsers for tarantool types. - `Converter`: this is a struct, that converts tuples using `fmt`: mappers list. - `tt_helpers`: these are auxiliary functions used to build `fmt` for the `Converter`. Relates to #1
- Loading branch information
Showing
9 changed files
with
1,536 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package tupleconv | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
// ErrUnexpectedValue is unexpected value error. | ||
type ErrUnexpectedValue struct { | ||
val any | ||
} | ||
|
||
// NewErrUnexpectedValue creates ErrUnexpectedValue by the value. | ||
func NewErrUnexpectedValue(val any) error { | ||
return &ErrUnexpectedValue{val: val} | ||
} | ||
|
||
// Error is the error implementation for ErrUnexpectedValue. | ||
func (err *ErrUnexpectedValue) Error() string { | ||
return fmt.Sprintf("unexpected value: %v", err.val) | ||
} | ||
|
||
var ErrWrongTupleLength = errors.New( | ||
"tuple length should be equal to the length of the mappers list", | ||
) | ||
|
||
var ErrInvalidFmt = errors.New("invalid fmt") | ||
|
||
// Converter performs tuple conversion. | ||
type Converter[T any] struct { | ||
fmt []Mapper[T] | ||
isSingle bool | ||
} | ||
|
||
// NewConverter creates Converter. | ||
func NewConverter[T any](fmt []Mapper[T]) (*Converter[T], error) { | ||
if len(fmt) == 0 { | ||
return nil, ErrInvalidFmt | ||
} | ||
conv := &Converter[T]{fmt: fmt} | ||
return conv, nil | ||
} | ||
|
||
// NewConverterSingle creates a Converter with a single type as a format. | ||
func NewConverterSingle[T any](fmt Mapper[T]) (*Converter[T], error) { | ||
conv := &Converter[T]{ | ||
fmt: []Mapper[T]{fmt}, | ||
isSingle: true, | ||
} | ||
return conv, nil | ||
} | ||
|
||
// validateTuple validates tuple in accordance with the Converter properties. | ||
func (conv *Converter[T]) validateTuple(tuple []T) error { | ||
if conv.isSingle { | ||
return nil | ||
} | ||
if len(conv.fmt) != len(tuple) { | ||
return ErrWrongTupleLength | ||
} | ||
return nil | ||
} | ||
|
||
// ConvertVerbose converts tuple. | ||
// It returns converted slice, conversion errors slice, API error. | ||
func (conv *Converter[T]) ConvertVerbose(tuple []T) ([]any, []error, error) { | ||
if err := conv.validateTuple(tuple); err != nil { | ||
return nil, nil, err | ||
} | ||
result := make([]any, len(tuple)) | ||
convErrors := make([]error, len(tuple)) | ||
for i, field := range tuple { | ||
c := conv.fmt[0] | ||
if !conv.isSingle { | ||
c = conv.fmt[i] | ||
} | ||
result[i], convErrors[i] = c.Map(field) | ||
} | ||
return result, convErrors, nil | ||
} | ||
|
||
// Convert converts tuple until the first error. | ||
func (conv *Converter[T]) Convert(tuple []T) ([]any, error) { | ||
if err := conv.validateTuple(tuple); err != nil { | ||
return nil, err | ||
} | ||
var err error | ||
result := make([]any, len(tuple)) | ||
for i, field := range tuple { | ||
c := conv.fmt[0] | ||
if !conv.isSingle { | ||
c = conv.fmt[i] | ||
} | ||
result[i], err = c.Map(field) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package tupleconv | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestValidateTuple(t *testing.T) { | ||
sampleMapper := NewFuncMapper(func(t string) (any, error) { | ||
return t + "1", nil | ||
}) | ||
|
||
singleConv, err := NewConverterSingle[string](sampleMapper) | ||
require.NoError(t, err) | ||
|
||
assert.NoError(t, singleConv.validateTuple([]string{"a", "b", "c"})) | ||
assert.NoError(t, singleConv.validateTuple([]string{""})) | ||
assert.NoError(t, singleConv.validateTuple([]string{"a"})) | ||
|
||
conv, err := NewConverter([]Mapper[string]{sampleMapper, sampleMapper}) | ||
require.NoError(t, err) | ||
|
||
assert.NoError(t, conv.validateTuple([]string{"a", "b"})) | ||
assert.Error(t, conv.validateTuple([]string{"a"})) | ||
assert.Error(t, conv.validateTuple([]string{"a", "b", "c"})) | ||
} | ||
|
||
func TestSingleConverter(t *testing.T) { | ||
stringer := NewFuncMapper(func(t any) (any, error) { | ||
return fmt.Sprintln(t), nil | ||
}) | ||
|
||
encoder, err := NewConverterSingle[any](stringer) | ||
require.NoError(t, err) | ||
|
||
cases := []struct { | ||
name string | ||
tuple []any | ||
expected []any | ||
}{ | ||
{ | ||
name: "empty", | ||
tuple: []any{}, | ||
expected: []any{}, | ||
}, | ||
{ | ||
name: "different types", | ||
tuple: []any{ | ||
"a", | ||
1, | ||
nil, | ||
map[string]string{ | ||
"1": "2", | ||
"3": "4", | ||
}, | ||
}, | ||
expected: []any{ | ||
"a\n", | ||
"1\n", | ||
"<nil>\n", | ||
"map[1:2 3:4]\n", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
actual, err := encoder.Convert(tc.tuple) | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expected, actual) | ||
|
||
actualVerbose, errorList, err := encoder.ConvertVerbose(tc.tuple) | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expected, actualVerbose) | ||
assert.Equal(t, len(tc.expected), len(errorList)) | ||
for _, err := range errorList { | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestConverter(t *testing.T) { | ||
idealMapper := NewFuncMapper(func(t string) (any, error) { | ||
return 42, nil | ||
}) | ||
|
||
someError := errors.New("some error") | ||
nonIdealMapper := NewFuncMapper(func(t string) (any, error) { | ||
if t == "bad" { | ||
return "", someError | ||
} | ||
return t, nil | ||
}) | ||
|
||
decoder, err := NewConverter([]Mapper[string]{idealMapper, nonIdealMapper}) | ||
require.NoError(t, err) | ||
|
||
t.Run("Convert", func(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
tuple []string | ||
expectedTuple []any | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "all is ok", | ||
tuple: []string{"1", "2"}, | ||
expectedTuple: []any{42, "2"}, | ||
}, | ||
{ | ||
name: "wrong tuple length", | ||
tuple: []string{"1"}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "decoding error", | ||
tuple: []string{"1", "bad"}, | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
actualTuple, err := decoder.Convert(tc.tuple) | ||
if tc.wantErr { | ||
assert.Error(t, err) | ||
} else { | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expectedTuple, actualTuple) | ||
} | ||
}) | ||
} | ||
}) | ||
|
||
t.Run("ConvertVerbose", func(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
tuple []string | ||
expectedTuple []any | ||
expectedErrorList []error | ||
wantAPIError bool | ||
}{ | ||
{ | ||
name: "all is ok", | ||
tuple: []string{"1", "2"}, | ||
expectedTuple: []any{42, "2"}, | ||
expectedErrorList: []error{nil, nil}, | ||
wantAPIError: false, | ||
}, | ||
{ | ||
name: "wrong tuple length", | ||
tuple: []string{"1", "2", "3", "4"}, | ||
wantAPIError: true, | ||
}, | ||
{ | ||
name: "decoding error", | ||
tuple: []string{"1000", "bad"}, | ||
expectedTuple: []any{42, ""}, | ||
expectedErrorList: []error{nil, someError}, | ||
}, | ||
} | ||
for _, tc := range cases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
actualTuple, errorList, err := decoder.ConvertVerbose(tc.tuple) | ||
if tc.wantAPIError { | ||
assert.Error(t, err) | ||
} else { | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expectedTuple, actualTuple) | ||
assert.Equal(t, tc.expectedErrorList, errorList) | ||
} | ||
}) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module github.com/tarantool/go-tupleconv | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/google/uuid v1.3.0 | ||
github.com/shopspring/decimal v1.3.1 | ||
github.com/stretchr/testify v1.7.1 | ||
github.com/tarantool/go-tarantool v1.12.0 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.0 // indirect | ||
github.com/golang/protobuf v1.3.1 // indirect | ||
github.com/mattn/go-pointer v0.0.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect | ||
github.com/tarantool/go-openssl v0.0.8-0.20230307065445-720eeb389195 // indirect | ||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect | ||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect | ||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect | ||
google.golang.org/appengine v1.6.7 // indirect | ||
gopkg.in/vmihailenco/msgpack.v2 v2.9.2 // indirect | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= | ||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= | ||
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= | ||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= | ||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | ||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= | ||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/tarantool/go-openssl v0.0.8-0.20230307065445-720eeb389195 h1:/AN3eUPsTlvF6W+Ng/8ZjnSU6o7L0H4Wb9GMks6RkzU= | ||
github.com/tarantool/go-openssl v0.0.8-0.20230307065445-720eeb389195/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A= | ||
github.com/tarantool/go-tarantool v1.12.0 h1:JmTJDppt1hvSrI0iZKMocgWlBWMvEkhFGUZCgau9wS8= | ||
github.com/tarantool/go-tarantool v1.12.0/go.mod h1:QRiXv0jnxwgxHtr9ZmifSr/eRba76gTUBgp69pDMX1U= | ||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= | ||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= | ||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= | ||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= | ||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= | ||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= | ||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/vmihailenco/msgpack.v2 v2.9.2 h1:gjPqo9orRVlSAH/065qw3MsFCDpH7fa1KpiizXyllY4= | ||
gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
Oops, something went wrong.