diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index eee281f..2ae10af 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -8,20 +8,20 @@ jobs: test: strategy: matrix: - go-version: [1.20.x, 1.19.x, 1.18.x] + go-version: [1.23.x, 1.22.x, 1.21.x, 1.20.x, 1.19.x, 1.18.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Restore Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-v1-go-${{ hashFiles('**/go.sum') }} @@ -32,7 +32,7 @@ jobs: run: go test -race -covermode=atomic -coverprofile="profile.cov" ./... - name: Send Coverage - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.20.x' + if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.23.x' uses: shogo82148/actions-goveralls@v1 with: path-to-profile: profile.cov @@ -41,8 +41,8 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: version: latest diff --git a/README.md b/README.md index 4ebde53..d217d50 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Package mold ============ -![Project status](https://img.shields.io/badge/version-4.5.0-green.svg) +![Project status](https://img.shields.io/badge/version-4.5.1-green.svg) [![Build Status](https://travis-ci.org/go-playground/mold.svg?branch=v2)](https://travis-ci.org/go-playground/mold) [![Coverage Status](https://coveralls.io/repos/github/go-playground/mold/badge.svg?branch=v2)](https://coveralls.io/github/go-playground/mold?branch=v2) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/mold)](https://goreportcard.com/report/github.com/go-playground/mold) diff --git a/go.mod b/go.mod index c04de80..b53564f 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,17 @@ module github.com/go-playground/mold/v4 go 1.18 require ( - github.com/go-playground/assert/v2 v2.0.1 + github.com/go-playground/assert/v2 v2.2.0 + github.com/gosimple/slug v1.15.0 github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 github.com/segmentio/go-snakecase v1.2.0 - github.com/stretchr/testify v1.7.0 - golang.org/x/text v0.6.0 + github.com/stretchr/testify v1.10.0 + golang.org/x/text v0.21.0 ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect - github.com/gosimple/slug v1.13.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9335f45..2dbf5ce 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -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/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= -github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= +github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -12,12 +12,11 @@ github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 h1:Cpx2WLIv github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734/go.mod h1:hqVOMAwu+ekffC3Tvq5N1ljnXRrFKcaSjbCmQ8JgYaI= github.com/segmentio/go-snakecase v1.2.0 h1:4cTmEjPGi03WmyAHWBjX53viTpBkn/z+4DO++fqYvpw= github.com/segmentio/go-snakecase v1.2.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modifiers/multi.go b/modifiers/multi.go index 9320f00..8f81d06 100644 --- a/modifiers/multi.go +++ b/modifiers/multi.go @@ -23,115 +23,121 @@ func defaultValue(ctx context.Context, fl mold.FieldLevel) error { return setValue(ctx, fl) } +func setValue(_ context.Context, fl mold.FieldLevel) error { + return setValueInner(fl.Field(), fl.Param()) +} + // setValue allows setting of a specified value -func setValue(ctx context.Context, fl mold.FieldLevel) error { - switch fl.Field().Kind() { +func setValueInner(field reflect.Value, param string) error { + switch field.Kind() { case reflect.String: - fl.Field().SetString(fl.Param()) + field.SetString(param) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: - value, err := strconv.Atoi(fl.Param()) + value, err := strconv.Atoi(param) if err != nil { return err } - fl.Field().SetInt(int64(value)) + field.SetInt(int64(value)) case reflect.Int64: var value int64 - if fl.Field().Type() == durationType { - d, err := time.ParseDuration(fl.Param()) + if field.Type() == durationType { + d, err := time.ParseDuration(param) if err != nil { return err } value = int64(d) } else { - i, err := strconv.Atoi(fl.Param()) + i, err := strconv.Atoi(param) if err != nil { return err } value = int64(i) } - fl.Field().SetInt(value) + field.SetInt(value) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - value, err := strconv.Atoi(fl.Param()) + value, err := strconv.Atoi(param) if err != nil { return err } - fl.Field().SetUint(uint64(value)) + field.SetUint(uint64(value)) case reflect.Float32, reflect.Float64: - value, err := strconv.ParseFloat(fl.Param(), 64) + value, err := strconv.ParseFloat(param, 64) if err != nil { return err } - fl.Field().SetFloat(value) + field.SetFloat(value) case reflect.Bool: - value, err := strconv.ParseBool(fl.Param()) + value, err := strconv.ParseBool(param) if err != nil { return err } - fl.Field().SetBool(value) + field.SetBool(value) case reflect.Map: var n int var err error - if fl.Param() != "" { - n, err = strconv.Atoi(fl.Param()) + if param != "" { + n, err = strconv.Atoi(param) if err != nil { return err } } - fl.Field().Set(reflect.MakeMapWithSize(fl.Field().Type(), n)) + field.Set(reflect.MakeMapWithSize(field.Type(), n)) case reflect.Slice: var cap int var err error - if fl.Param() != "" { - cap, err = strconv.Atoi(fl.Param()) + if param != "" { + cap, err = strconv.Atoi(param) if err != nil { return err } } - fl.Field().Set(reflect.MakeSlice(fl.Field().Type(), 0, cap)) + field.Set(reflect.MakeSlice(field.Type(), 0, cap)) case reflect.Struct: - if fl.Field().Type() == timeType { - if fl.Param() != "" { - if strings.ToLower(fl.Param()) == "utc" { - fl.Field().Set(reflect.ValueOf(time.Now().UTC())) + if field.Type() == timeType { + if param != "" { + if strings.ToLower(param) == "utc" { + field.Set(reflect.ValueOf(time.Now().UTC())) } else { - t, err := time.Parse(time.RFC3339Nano, fl.Param()) + t, err := time.Parse(time.RFC3339Nano, param) if err != nil { return err } - fl.Field().Set(reflect.ValueOf(t)) + field.Set(reflect.ValueOf(t)) } } else { - fl.Field().Set(reflect.ValueOf(time.Now())) + field.Set(reflect.ValueOf(time.Now())) } } case reflect.Chan: var buffer int var err error - if fl.Param() != "" { - buffer, err = strconv.Atoi(fl.Param()) + if param != "" { + buffer, err = strconv.Atoi(param) if err != nil { return err } } - fl.Field().Set(reflect.MakeChan(fl.Field().Type(), buffer)) + field.Set(reflect.MakeChan(field.Type(), buffer)) case reflect.Ptr: - fl.Field().Set(reflect.New(fl.Field().Type().Elem())) + + field.Set(reflect.New(field.Type().Elem())) + return setValueInner(field.Elem(), param) } return nil } // empty sets the field to the zero value of the field type -func empty(ctx context.Context, fl mold.FieldLevel) error { +func empty(_ context.Context, fl mold.FieldLevel) error { zeroValue := reflect.Zero(fl.Field().Type()) fl.Field().Set(zeroValue) return nil diff --git a/modifiers/multi_test.go b/modifiers/multi_test.go index 201a46c..014c0ab 100644 --- a/modifiers/multi_test.go +++ b/modifiers/multi_test.go @@ -169,6 +169,20 @@ func TestDefaultSetSpecialTypes(t *testing.T) { }, }, + { + name: "set *time.Time to value", + field: (*time.Time)(nil), + tags: "set=2023-05-28T15:50:31Z", + vf: func(field interface{}) { + m := field.(time.Time) + Equal(t, m.Location(), time.UTC) + + tm, err := time.Parse(time.RFC3339Nano, "2023-05-28T15:50:31Z") + Equal(t, err, nil) + Equal(t, tm.Equal(m), true) + + }, + }, { name: "default pointer to slice", field: (*[]string)(nil), @@ -186,6 +200,32 @@ func TestDefaultSetSpecialTypes(t *testing.T) { m := field.([]string) Equal(t, len(m), 0) }, + }, { + name: "default pointer to int", + field: (*int)(nil), + tags: "default=5", + vf: func(field interface{}) { + m := field.(int) + Equal(t, m, 5) + }, + }, + { + name: "default pointer to string", + field: (*string)(nil), + tags: "default=test", + vf: func(field interface{}) { + m := field.(string) + Equal(t, m, "test") + }, + }, + { + name: "set pointer to string", + field: (*string)(nil), + tags: "set", + vf: func(field interface{}) { + m := field.(string) + Equal(t, m, "") + }, }, } @@ -409,6 +449,42 @@ func TestDefault(t *testing.T) { tags: "default=blue", expectError: true, }, + { + name: "default nil pointer to int", + field: (*int)(nil), + tags: "default=3", + expected: 3, + }, + { + name: "default not nil pointer to int", + field: newPointer(1), + tags: "default=3", + expected: 1, + }, + { + name: "default nil pointer to string", + field: (*string)(nil), + tags: "default=test", + expected: "test", + }, + { + name: "default not nil pointer to string", + field: newPointer("existing_value"), + tags: "default=test", + expected: "existing_value", + }, + { + name: "default nil pointer to bool", + field: (*bool)(nil), + tags: "default=true", + expected: true, + }, + { + name: "default not nil pointer to bool", + field: newPointer(true), + tags: "default=true", + expected: true, + }, } for _, tc := range tests { @@ -500,3 +576,7 @@ func TestEmpty(t *testing.T) { }) } } + +func newPointer[T any](value T) *T { + return &value +}