Skip to content

Commit

Permalink
tupleconv: implement basics
Browse files Browse the repository at this point in the history
- `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
askalt committed Jul 27, 2023
1 parent d9cb855 commit 2bde0bc
Show file tree
Hide file tree
Showing 9 changed files with 1,536 additions and 0 deletions.
100 changes: 100 additions & 0 deletions converter.go
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
}
179 changes: 179 additions & 0 deletions converter_test.go
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)
}
})
}
})
}
26 changes: 26 additions & 0 deletions go.mod
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
)
53 changes: 53 additions & 0 deletions go.sum
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=
Loading

0 comments on commit 2bde0bc

Please sign in to comment.