Skip to content

Commit

Permalink
feat: add custom type encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkgos committed May 28, 2024
1 parent 4fa1a6e commit f8c8bf8
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 0 deletions.
129 changes: 129 additions & 0 deletions form/form_custom_type_decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package form

import (
"strconv"
"strings"

"github.com/go-playground/form/v4"
"golang.org/x/exp/constraints"
)

func RegisterBuiltinSliceTypeDecoderComma(dec *form.Decoder) {
dec.RegisterCustomTypeFunc(DecodeCustomIntSlice[int], []int{})
dec.RegisterCustomTypeFunc(DecodeCustomInt8Slice[int8], []int8{})
dec.RegisterCustomTypeFunc(DecodeCustomInt16Slice[int16], []int16{})
dec.RegisterCustomTypeFunc(DecodeCustomInt32Slice[int32], []int32{})
dec.RegisterCustomTypeFunc(DecodeCustomInt64Slice[int64], []int64{})
dec.RegisterCustomTypeFunc(DecodeCustomUintSlice[uint], []uint{})
dec.RegisterCustomTypeFunc(DecodeCustomUint8Slice[uint8], []uint8{})
dec.RegisterCustomTypeFunc(DecodeCustomUint16Slice[uint16], []uint16{})
dec.RegisterCustomTypeFunc(DecodeCustomUint32Slice[uint32], []uint32{})
dec.RegisterCustomTypeFunc(DecodeCustomUint64Slice[uint64], []uint64{})
dec.RegisterCustomTypeFunc(DecodeCustomFloat32Slice[float32], []float32{})
dec.RegisterCustomTypeFunc(DecodeCustomFloat64Slice[float64], []float64{})
dec.RegisterCustomTypeFunc(DecodeCustomStringSlice[string], []string{})
}

//* encoder: custom type number/string slice/array

func DecodeCustomUintSlice[T ~uint](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (uint64, error) {
return strconv.ParseUint(s, 10, 0)
})
}

func DecodeCustomUint8Slice[T ~uint8](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (uint64, error) {
return strconv.ParseUint(s, 10, 8)
})
}

func DecodeCustomUint16Slice[T ~uint16](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (uint64, error) {
return strconv.ParseUint(s, 10, 16)
})
}

func DecodeCustomUint32Slice[T ~uint32](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (uint64, error) {
return strconv.ParseUint(s, 10, 32)
})
}

func DecodeCustomUint64Slice[T ~uint64](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (uint64, error) {
return strconv.ParseUint(s, 10, 64)
})
}

func DecodeCustomIntSlice[T ~int](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (int64, error) {
return strconv.ParseInt(s, 10, 0)
})
}

func DecodeCustomInt8Slice[T ~int8](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (int64, error) {
return strconv.ParseInt(s, 10, 8)
})
}

func DecodeCustomInt16Slice[T ~int16](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (int64, error) {
return strconv.ParseInt(s, 10, 16)
})
}

func DecodeCustomInt32Slice[T ~int32](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (int64, error) {
return strconv.ParseInt(s, 10, 32)
})
}

func DecodeCustomInt64Slice[T ~int64](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (int64, error) {
return strconv.ParseInt(s, 10, 64)
})
}

func DecodeCustomFloat64Slice[T ~float64](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (float64, error) {
return strconv.ParseFloat(s, 64)
})
}

func DecodeCustomFloat32Slice[T ~float32](values []string) (any, error) {
return decodeNumber[T](values, func(s string) (float64, error) {
return strconv.ParseFloat(s, 32)
})
}

func DecodeCustomStringSlice[T ~string](values []string) (any, error) {
if len(values) == 0 {
return []T{}, nil
}
ret := make([]T, 0, 32)
for _, s := range values {
for _, v := range strings.Split(s, ",") {
ret = append(ret, T(v))
}
}
return ret, nil
}

func decodeNumber[T constraints.Integer | constraints.Float, V ~uint64 | ~int64 | ~float64](values []string, parse func(string) (V, error)) (any, error) {
if len(values) == 0 {
return []T{}, nil
}
ret := make([]T, 0, 32)
for _, s := range values {
for _, v := range strings.Split(s, ",") {
i, err := parse(v)
if err != nil {
return nil, err
}
ret = append(ret, T(i))
}
}
return ret, nil
}
103 changes: 103 additions & 0 deletions form/form_custom_type_decoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package form

import (
"net/url"
"testing"

"github.com/go-playground/form/v4"
"github.com/stretchr/testify/require"
)

type CustomInt int
type CustomInt8 int8
type CustomInt16 int16
type CustomInt32 int32
type CustomInt64 int64
type CustomUint uint
type CustomUint8 uint8
type CustomUint16 uint16
type CustomUint32 uint32
type CustomUint64 uint64
type CustomFloat32 float32
type CustomFloat64 float64
type CustomString string

type customDecode struct {
I []int `json:"i"`
I8 []int8 `json:"i8"`
I16 []int16 `json:"i16"`
I32 []int32 `json:"i32"`
I64 []int64 `json:"i64"`
U []int `json:"u"`
U8 []int8 `json:"u8"`
U16 []int16 `json:"u16"`
U32 []int32 `json:"u32"`
U64 []int64 `json:"u64"`
F32 []float32 `json:"f32"`
F64 []float64 `json:"f64"`
S []string `json:"s"`
Ci []CustomInt `json:"ci"`
Ci8 []CustomInt8 `json:"ci8"`
Ci16 []CustomInt16 `json:"ci16"`
Ci32 []CustomInt32 `json:"ci32"`
Ci64 []CustomInt64 `json:"ci64"`
Cu []CustomUint `json:"cu"`
Cu8 []CustomUint8 `json:"cu8"`
Cu16 []CustomUint16 `json:"cu16"`
Cu32 []CustomUint32 `json:"cu32"`
Cu64 []CustomUint64 `json:"cu64"`
Cf32 []CustomFloat32 `json:"cf32"`
Cf64 []CustomFloat64 `json:"cf64"`
Cs []CustomString `json:"cs"`
}

func Test_CustomTypeDecoder(t *testing.T) {
dec := form.NewDecoder()
dec.SetTagName("json")

RegisterBuiltinSliceTypeDecoderComma(dec)
dec.RegisterCustomTypeFunc(DecodeCustomIntSlice[CustomInt], []CustomInt{})
dec.RegisterCustomTypeFunc(DecodeCustomInt8Slice[CustomInt8], []CustomInt8{})
dec.RegisterCustomTypeFunc(DecodeCustomInt16Slice[CustomInt16], []CustomInt16{})
dec.RegisterCustomTypeFunc(DecodeCustomInt32Slice[CustomInt32], []CustomInt32{})
dec.RegisterCustomTypeFunc(DecodeCustomInt64Slice[CustomInt64], []CustomInt64{})
dec.RegisterCustomTypeFunc(DecodeCustomUintSlice[CustomUint], []CustomUint{})
dec.RegisterCustomTypeFunc(DecodeCustomUint8Slice[CustomUint8], []CustomUint8{})
dec.RegisterCustomTypeFunc(DecodeCustomUint16Slice[CustomUint16], []CustomUint16{})
dec.RegisterCustomTypeFunc(DecodeCustomUint32Slice[CustomUint32], []CustomUint32{})
dec.RegisterCustomTypeFunc(DecodeCustomUint64Slice[CustomUint64], []CustomUint64{})
dec.RegisterCustomTypeFunc(DecodeCustomFloat32Slice[CustomFloat32], []CustomFloat32{})
dec.RegisterCustomTypeFunc(DecodeCustomFloat64Slice[CustomFloat64], []CustomFloat64{})
dec.RegisterCustomTypeFunc(DecodeCustomStringSlice[CustomString], []CustomString{})
got := customDecode{}
err := dec.Decode(&got, url.Values{
"i": []string{"1,2,3", "4,5,6"},
"i8": []string{"81,82,83"},
"i16": []string{"161,162,163"},
"i32": []string{"321,322,323"},
"i64": []string{"641,642,643"},
"u": []string{"111111,222222,333333"},
"u8": []string{"11,22,33"},
"u16": []string{"1611,1622,1633"},
"u32": []string{"3211,3222,3233"},
"u64": []string{"6411,6422,6433"},
"f32": []string{"1.1,1.2,1.3"},
"f64": []string{"2.1,2.2,2.3"},
"s": []string{"a,b,c", "d,e,f"},
"ci": []string{"1,2,3"},
"ci8": []string{"81,82,83"},
"ci16": []string{"161,162,163"},
"ci32": []string{"321,322,323"},
"ci64": []string{"641,642,643"},
"cu": []string{"111111,222222,333333"},
"cu8": []string{"11,22,33"},
"cu16": []string{"1611,1622,1633"},
"cu32": []string{"3211,3222,3233"},
"cu64": []string{"6411,6422,6433"},
"cf32": []string{"1.1,1.2,1.3"},
"cf64": []string{"2.1,2.2,2.3"},
"cs": []string{"a,b,c"},
})
require.NoError(t, err)
t.Logf("%#v", got)
}
76 changes: 76 additions & 0 deletions form/form_custom_type_encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package form

import (
"fmt"
"strconv"
"strings"

"golang.org/x/exp/constraints"
)

//* encoder: custom type number/string slice/array

func EncodeCustomUnsignedIntegerSlice[T constraints.Unsigned](x any) ([]string, error) {
return encodeCustomNumberSlice[T](x, func(u uint64) string {
return strconv.FormatUint(u, 10)
})
}

func EncodeCustomSignedIntegerSlice[T constraints.Unsigned](x any) ([]string, error) {
return encodeCustomNumberSlice[T](x, func(u int64) string {
return strconv.FormatInt(u, 10)
})
}

func EncodeCustomFloat32Slice[T ~float32](x any) ([]string, error) {
return encodeCustomNumberSlice[T](x, func(u float64) string {
return strconv.FormatFloat(u, 'f', -1, 32)
})
}

func EncodeCustomFloat64Slice[T ~float64](x any) ([]string, error) {
return encodeCustomNumberSlice[T](x, func(u float64) string {
return strconv.FormatFloat(u, 'f', -1, 64)
})
}

func EncodeCustomStringSlice[T ~string](x any) ([]string, error) {
vs, ok := x.([]T)
if !ok {
return nil, fmt.Errorf("")
}
has := false
b := strings.Builder{}
for _, vv := range vs {
if vv == "" {
continue
}
if has {
b.WriteString(",")
}
has = true
b.WriteString(string(vv))
}
return []string{b.String()}, nil
}

func encodeCustomNumberSlice[T constraints.Integer | constraints.Float, V uint64 | int64 | float64](x any, format func(V) string) ([]string, error) {
vs, ok := x.([]T)
if !ok {
return nil, fmt.Errorf("")
}
has := false
b := strings.Builder{}
for _, v := range vs {
vv := format(V(v))
if vv == "" {
continue
}
if has {
b.WriteString(",")
}
has = true
b.WriteString(vv)
}
return []string{b.String()}, nil
}
1 change: 1 addition & 0 deletions form/form_custom_type_encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package form
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/spf13/cast v1.6.0
github.com/stretchr/testify v1.9.0
github.com/ugorji/go/codec v1.2.12
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be
google.golang.org/protobuf v1.34.1
gopkg.in/yaml.v3 v3.0.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU=
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
Expand Down

0 comments on commit f8c8bf8

Please sign in to comment.