Skip to content

Commit

Permalink
Rename struct tags and unify map and array rules
Browse files Browse the repository at this point in the history
  • Loading branch information
muXxer committed Nov 7, 2023
1 parent 40ac86b commit 0322ba6
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 118 deletions.
2 changes: 1 addition & 1 deletion serializer/serix/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ func (api *API) decodeMap(ctx context.Context, b []byte, value reflect.Value,
return consumedBytes, err
}

if err := api.checkMapMinMaxBounds(value.Len(), ts); err != nil {
if err := api.checkMinMaxBounds(value, ts); err != nil {
return consumedBytes, err
}

Expand Down
5 changes: 2 additions & 3 deletions serializer/serix/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,12 @@ func (api *API) encodeMapKVPair(ctx context.Context, key, val reflect.Value, ts

func (api *API) encodeMap(ctx context.Context, value reflect.Value, valueType reflect.Type,
ts TypeSettings, opts *options) ([]byte, error) {
size := value.Len()

if err := api.checkMapMinMaxBounds(size, ts); err != nil {
if err := api.checkMinMaxBounds(value, ts); err != nil {
return nil, err
}

data := make([][]byte, size)
data := make([][]byte, value.Len())
iter := value.MapRange()
for i := 0; iter.Next(); i++ {
key := iter.Key()
Expand Down
4 changes: 2 additions & 2 deletions serializer/serix/map_decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ func (api *API) mapDecodeStructFields(
continue
}

fieldKey := fieldKeyString(sField.name)
fieldKey := FieldKeyString(sField.name)
if sField.settings.ts.fieldKey != nil {
fieldKey = sField.settings.ts.MustFieldKey()
}
Expand Down Expand Up @@ -484,7 +484,7 @@ func (api *API) mapDecodeMap(ctx context.Context, mapVal any, value reflect.Valu
value.SetMapIndex(keyValue, elemValue)
}

if err := api.checkMapMinMaxBounds(value.Len(), ts); err != nil {
if err := api.checkMinMaxBounds(value, ts); err != nil {
return err
}

Expand Down
6 changes: 2 additions & 4 deletions serializer/serix/map_encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (api *API) mapEncodeStructFields(
obj.Set(k, lo.Return1(castedEleOut.Get(k)))
}
default:
obj.Set(fieldKeyString(sField.name), eleOut)
obj.Set(FieldKeyString(sField.name), eleOut)
}
}

Expand Down Expand Up @@ -299,9 +299,7 @@ func (api *API) mapEncodeMapKVPair(ctx context.Context, key, val reflect.Value,
}

func (api *API) mapEncodeMap(ctx context.Context, value reflect.Value, ts TypeSettings, opts *options) (*orderedmap.OrderedMap, error) {
size := value.Len()

if err := api.checkMapMinMaxBounds(size, ts); err != nil {
if err := api.checkMinMaxBounds(value, ts); err != nil {
return nil, err
}

Expand Down
72 changes: 26 additions & 46 deletions serializer/serix/serix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ In order for a field to be detected by serix it must have `serix` struct tag set
serix traverses all fields and handles them in the order specified in the struct.
Apart from the required position you can provide the following settings to serix via struct tags:
"optional" - means that field might be nil. Only valid for pointers or interfaces: `serix:"example,optional"`
"lengthPrefixType=uint32" - provide serializer.SeriLengthPrefixType for that field: `serix:"example,lengthPrefixType=unint32"`
"lenPrefix=uint32" - provide serializer.SeriLengthPrefixType for that field: `serix:"example,lenPrefix=unint32"`
"nest" - handle embedded/anonymous field as a nested field: `serix:"example,nest"`
See serix_text.go for more detail.
*/
Expand All @@ -31,10 +31,6 @@ import (
)

var (
// ErrMapValidationMinElementsNotReached gets returned if the count of elements is too small.
ErrMapValidationMinElementsNotReached = ierrors.New("min count of elements within the map not reached")
// ErrMapValidationMaxElementsExceeded gets returned if the count of elements is too big.
ErrMapValidationMaxElementsExceeded = ierrors.New("max count of elements within the map exceeded")
// ErrMapValidationMaxBytesExceeded gets returned if the serialized byte size of the map is too big.
ErrMapValidationMaxBytesExceeded = ierrors.New("max bytes size of the map exceeded")
// ErrMapValidationViolatesUniqueness gets returned if the map elements are not unique.
Expand Down Expand Up @@ -204,21 +200,6 @@ func (api *API) checkMinMaxBounds(v reflect.Value, ts TypeSettings) error {
return nil
}

// checkMapMinMaxBounds checks whether the given map is within its defined bounds in case it has defined map rules.
func (api *API) checkMapMinMaxBounds(length int, ts TypeSettings) error {
if ts.mapRules != nil {
switch {
case ts.mapRules.MaxEntries > 0 && uint(length) > ts.mapRules.MaxEntries:
return ierrors.Wrapf(ErrMapValidationMaxElementsExceeded, "map (len %d) exceeds max length of %d ", length, ts.mapRules.MaxEntries)

case ts.mapRules.MinEntries > 0 && uint(length) < ts.mapRules.MinEntries:
return ierrors.Wrapf(ErrMapValidationMinElementsNotReached, "map (len %d) is less than min length of %d ", length, ts.mapRules.MinEntries)
}
}

return nil
}

// checkMapMaxByteSize checks whether the given map is within its defined max byte size in case it has defined map rules.
func (api *API) checkMapMaxByteSize(byteSize int, ts TypeSettings) error {
if ts.mapRules != nil && ts.mapRules.MaxByteSize > 0 && byteSize > int(ts.mapRules.MaxByteSize) {
Expand Down Expand Up @@ -863,6 +844,13 @@ func parseStructTag(tag string, serixPosition int) (tagSettings, error) {
case "omitempty":
settings.omitEmpty = true

case "lenPrefix":
value, err := parseStructTagValuePrefixType("lenPrefix", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
settings.ts = settings.ts.WithLengthPrefixType(value)

case "minLen":
value, err := parseStructTagValueUint("minLen", keyValue, currentPart)
if err != nil {
Expand All @@ -877,36 +865,15 @@ func parseStructTag(tag string, serixPosition int) (tagSettings, error) {
}
settings.ts = settings.ts.WithMaxLen(value)

case "lengthPrefixType":
value, err := parseStructTagValuePrefixType("lengthPrefixType", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
settings.ts = settings.ts.WithLengthPrefixType(value)

case "mapMinEntries":
value, err := parseStructTagValueUint("mapMinEntries", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
settings.ts = settings.ts.WithMapMinEntries(value)

case "mapMaxEntries":
value, err := parseStructTagValueUint("mapMaxEntries", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
settings.ts = settings.ts.WithMapMaxEntries(value)

case "mapMaxByteSize":
value, err := parseStructTagValueUint("mapMaxByteSize", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
settings.ts = settings.ts.WithMapMaxByteSize(value)

case "mapKeyLengthPrefixType":
value, err := parseStructTagValuePrefixType("mapKeyLengthPrefixType", keyValue, currentPart)
case "mapKeyLenPrefix":
value, err := parseStructTagValuePrefixType("mapKeyLenPrefix", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
Expand All @@ -926,8 +893,8 @@ func parseStructTag(tag string, serixPosition int) (tagSettings, error) {
}
settings.ts = settings.ts.WithMapKeyMaxLen(value)

case "mapValueLengthPrefixType":
value, err := parseStructTagValuePrefixType("mapValueLengthPrefixType", keyValue, currentPart)
case "mapValueLenPrefix":
value, err := parseStructTagValuePrefixType("mapValueLenPrefix", keyValue, currentPart)
if err != nil {
return tagSettings{}, err
}
Expand All @@ -950,6 +917,7 @@ func parseStructTag(tag string, serixPosition int) (tagSettings, error) {
default:
return tagSettings{}, ierrors.Errorf("unknown tag part: %s", currentPart)
}

seenParts[partName] = struct{}{}
}

Expand Down Expand Up @@ -1032,6 +1000,18 @@ func getNumberTypeToConvert(kind reflect.Kind) (int, reflect.Type, reflect.Type)
return bitSize, numberType, reflect.PointerTo(numberType)
}

func fieldKeyString(str string) string {
// FieldKeyString converts the given string to camelCase.
// Special keywords like ID or URL are converted to only first letter upper case.
func FieldKeyString(str string) string {
for _, keyword := range []string{"ID", "URL"} {
if !strings.Contains(str, keyword) {
continue
}

// first keyword letter upper case, rest lower case
str = strings.ReplaceAll(str, keyword, string(keyword[0])+strings.ToLower(keyword)[1:])
}

// first letter lower case
return strings.ToLower(str[:1]) + str[1:]
}
4 changes: 2 additions & 2 deletions serializer/serix/serix_serializable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ func (bs Bools) FromSerializables(seris serializer.Serializables) {
type SimpleStruct struct {
Bool bool `serix:"bool"`
Uint uint64 `serix:"uint"`
String string `serix:"string,lengthPrefixType=uint16"`
Bytes []byte `serix:"bytes,lengthPrefixType=uint32"`
String string `serix:"string,lenPrefix=uint16"`
Bytes []byte `serix:"bytes,lenPrefix=uint32"`
BytesArray [16]byte `serix:"bytesarray"`
BigInt *big.Int `serix:"bigint"`
Time time.Time `serix:"time"`
Expand Down
94 changes: 83 additions & 11 deletions serializer/serix/serix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestMinMax(t *testing.T) {
name: "ok - string in bounds",
paras: func() paras {
type example struct {
Str string `serix:"str,minLen=5,maxLen=10,lengthPrefixType=uint8"`
Str string `serix:"str,minLen=5,maxLen=10,lenPrefix=uint8"`
}

api := serix.NewAPI()
Expand All @@ -57,7 +57,7 @@ func TestMinMax(t *testing.T) {
name: "err - string out of bounds",
paras: func() paras {
type example struct {
Str string `serix:"str,minLen=5,maxLen=10,lengthPrefixType=uint8"`
Str string `serix:"str,minLen=5,maxLen=10,lenPrefix=uint8"`
}

api := serix.NewAPI()
Expand All @@ -75,7 +75,7 @@ func TestMinMax(t *testing.T) {
name: "ok - slice in bounds",
paras: func() paras {
type example struct {
Slice []byte `serix:"slice,minLen=0,maxLen=10,lengthPrefixType=uint8"`
Slice []byte `serix:"slice,minLen=0,maxLen=10,lenPrefix=uint8"`
}

api := serix.NewAPI()
Expand All @@ -93,7 +93,7 @@ func TestMinMax(t *testing.T) {
name: "err - slice out of bounds",
paras: func() paras {
type example struct {
Slice []byte `serix:"slice,minLen=0,maxLen=3,lengthPrefixType=uint8"`
Slice []byte `serix:"slice,minLen=0,maxLen=3,lenPrefix=uint8"`
}

api := serix.NewAPI()
Expand Down Expand Up @@ -181,7 +181,7 @@ func TestSerixMapSerialize(t *testing.T) {
type MyMapType map[string]string

type MapStruct struct {
MyMap MyMapType `serix:"myMap,lengthPrefixType=uint8,mapMinEntries=2,mapMaxEntries=4,mapMaxByteSize=50,mapKeyLengthPrefixType=uint16,mapKeyMinLen=2,mapKeyMaxLen=5,mapValueLengthPrefixType=uint32,mapValueMinLen=1,mapValueMaxLen=6"`
MyMap MyMapType `serix:"myMap,lenPrefix=uint8,minLen=2,maxLen=4,mapMaxByteSize=50,mapKeyLenPrefix=uint16,mapKeyMinLen=2,mapKeyMaxLen=5,mapValueLenPrefix=uint32,mapValueMinLen=1,mapValueMaxLen=6"`
}
testAPI.RegisterTypeSettings(MapStruct{}, serix.TypeSettings{})

Expand All @@ -207,7 +207,7 @@ func TestSerixMapSerialize(t *testing.T) {
},
target: &MapStruct{},
size: 0,
seriErr: serix.ErrMapValidationMinElementsNotReached,
seriErr: serializer.ErrArrayValidationMinElementsNotReached,
},
{
name: "fail - too many entries",
Expand All @@ -222,7 +222,7 @@ func TestSerixMapSerialize(t *testing.T) {
},
target: &MapStruct{},
size: 0,
seriErr: serix.ErrMapValidationMaxElementsExceeded,
seriErr: serializer.ErrArrayValidationMaxElementsExceeded,
},
{
name: "fail - too big",
Expand Down Expand Up @@ -341,12 +341,12 @@ func TestSerixMapDeserialize(t *testing.T) {

// used to create test data
type TestVectorMapStruct struct {
MyMap MyMapType `serix:"myMap,lengthPrefixType=uint8,mapMinEntries=1,mapMaxEntries=5,mapMaxByteSize=100,mapKeyLengthPrefixType=uint16,mapKeyMinLen=1,mapKeyMaxLen=7,mapValueLengthPrefixType=uint32,mapValueMinLen=0,mapValueMaxLen=10"`
MyMap MyMapType `serix:"myMap,lenPrefix=uint8,minLen=1,maxLen=5,mapMaxByteSize=100,mapKeyLenPrefix=uint16,mapKeyMinLen=1,mapKeyMaxLen=7,mapValueLenPrefix=uint32,mapValueMinLen=0,mapValueMaxLen=10"`
}
testAPI.RegisterTypeSettings(TestVectorMapStruct{}, serix.TypeSettings{})

type MapStruct struct {
MyMap MyMapType `serix:"myMap,lengthPrefixType=uint8,mapMinEntries=2,mapMaxEntries=4,mapMaxByteSize=50,mapKeyLengthPrefixType=uint16,mapKeyMinLen=2,mapKeyMaxLen=5,mapValueLengthPrefixType=uint32,mapValueMinLen=1,mapValueMaxLen=6"`
MyMap MyMapType `serix:"myMap,lenPrefix=uint8,minLen=2,maxLen=4,mapMaxByteSize=50,mapKeyLenPrefix=uint16,mapKeyMinLen=2,mapKeyMaxLen=5,mapValueLenPrefix=uint32,mapValueMinLen=1,mapValueMaxLen=6"`
}
testAPI.RegisterTypeSettings(MapStruct{}, serix.TypeSettings{})

Expand All @@ -372,7 +372,7 @@ func TestSerixMapDeserialize(t *testing.T) {
},
target: &MapStruct{},
size: 0,
deSeriErr: serix.ErrMapValidationMinElementsNotReached,
deSeriErr: serializer.ErrArrayValidationMinElementsNotReached,
},
{
name: "fail - too many entries",
Expand All @@ -387,7 +387,7 @@ func TestSerixMapDeserialize(t *testing.T) {
},
target: &MapStruct{},
size: 0,
deSeriErr: serix.ErrMapValidationMaxElementsExceeded,
deSeriErr: serializer.ErrArrayValidationMaxElementsExceeded,
},
{
name: "fail - too big",
Expand Down Expand Up @@ -457,3 +457,75 @@ func TestSerixMapDeserialize(t *testing.T) {
t.Run(tt.name, tt.run)
}
}

func TestSerixFieldKeyString(t *testing.T) {
type test struct {
name string
source string
target string
}

tests := []*test{
{
name: "single char",
source: "A",
target: "a",
},
{
name: "all upper case",
source: "MYTEST",
target: "mYTEST",
},
{
name: "all lower case",
source: "mytest",
target: "mytest",
},
{
name: "mixed case",
source: "MyTest",
target: "myTest",
},
{
name: "mixed case with numbers",
source: "MyTest123",
target: "myTest123",
},
{
name: "mixed case with numbers and underscore",
source: "MyTest_123",
target: "myTest_123",
},
{
name: "mixed case with numbers and underscore and dash",
source: "MyTest_123-",
target: "myTest_123-",
},
{
name: "mixed case with special keyword 'id'",
source: "MyTestID",
target: "myTestId",
},
{
name: "mixed case with special keyword 'URL'",
source: "MyTestURL",
target: "myTestUrl",
},
{
name: "mixed case with special keyword 'ID' but lowercase",
source: "MyTestid",
target: "myTestid",
},
{
name: "mixed case with special keyword 'URL' but lowercase",
source: "MyTesturl",
target: "myTesturl",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.target, serix.FieldKeyString(tt.source))
})
}
}
Loading

0 comments on commit 0322ba6

Please sign in to comment.