Skip to content

Commit 4214131

Browse files
author
Dean Karn
authored
Handle ptr smart ptrs (#44)
- Added new default & set types for `chan`, `map`, `slice`, `time.Time` and `Pointer` types. - Updated to handle pointer to a `Slice` or `Map` for situations where code is not under your control. Fixes #43
1 parent 05050dc commit 4214131

File tree

4 files changed

+267
-1
lines changed

4 files changed

+267
-1
lines changed

Diff for: README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package mold
22
============
3-
![Project status](https://img.shields.io/badge/version-4.4.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-4.5.0-green.svg)
44
[![Build Status](https://travis-ci.org/go-playground/mold.svg?branch=v2)](https://travis-ci.org/go-playground/mold)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/mold/badge.svg?branch=v2)](https://coveralls.io/github/go-playground/mold?branch=v2)
66
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/mold)](https://goreportcard.com/report/github.com/go-playground/mold)
@@ -58,7 +58,14 @@ These functions modify the data in-place.
5858
| ucase | Uppercases the data. |
5959
| ucfirst | Upper cases the first character of the data. |
6060

61+
**Special Notes:**
62+
`default` and `set` modifiers are special in that they can be used to set the value of a field or underlying type information or attributes and both use the same underlying function to set the data.
6163

64+
Setting a Param will have the following special effects on data types where it's not just the value being set:
65+
- Chan - param used to set the buffer size, default = 0.
66+
- Slice - param used to set the capacity, default = 0.
67+
- Map - param used to set the size, default = 0.
68+
- time.Time - param used to set the time format OR value, default = time.Now(), `utc` = time.Now().UTC(), other tries to parse using RFC3339Nano and set a time value.
6269

6370
Scrubbers
6471
----------

Diff for: modifiers/multi.go

+53
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"context"
55
"reflect"
66
"strconv"
7+
"strings"
78
"time"
89

910
"github.com/go-playground/mold/v4"
1011
)
1112

1213
var (
1314
durationType = reflect.TypeOf(time.Duration(0))
15+
timeType = reflect.TypeOf(time.Time{})
1416
)
1517

1618
// defaultValue allows setting of a default value IF no value is already present.
@@ -73,6 +75,57 @@ func setValue(ctx context.Context, fl mold.FieldLevel) error {
7375
}
7476
fl.Field().SetBool(value)
7577

78+
case reflect.Map:
79+
var n int
80+
var err error
81+
if fl.Param() != "" {
82+
n, err = strconv.Atoi(fl.Param())
83+
if err != nil {
84+
return err
85+
}
86+
}
87+
fl.Field().Set(reflect.MakeMapWithSize(fl.Field().Type(), n))
88+
89+
case reflect.Slice:
90+
var cap int
91+
var err error
92+
if fl.Param() != "" {
93+
cap, err = strconv.Atoi(fl.Param())
94+
if err != nil {
95+
return err
96+
}
97+
}
98+
fl.Field().Set(reflect.MakeSlice(fl.Field().Type(), 0, cap))
99+
100+
case reflect.Struct:
101+
if fl.Field().Type() == timeType {
102+
if fl.Param() != "" {
103+
if strings.ToLower(fl.Param()) == "utc" {
104+
fl.Field().Set(reflect.ValueOf(time.Now().UTC()))
105+
} else {
106+
t, err := time.Parse(time.RFC3339Nano, fl.Param())
107+
if err != nil {
108+
return err
109+
}
110+
fl.Field().Set(reflect.ValueOf(t))
111+
}
112+
} else {
113+
fl.Field().Set(reflect.ValueOf(time.Now()))
114+
}
115+
}
116+
case reflect.Chan:
117+
var buffer int
118+
var err error
119+
if fl.Param() != "" {
120+
buffer, err = strconv.Atoi(fl.Param())
121+
if err != nil {
122+
return err
123+
}
124+
}
125+
fl.Field().Set(reflect.MakeChan(fl.Field().Type(), buffer))
126+
127+
case reflect.Ptr:
128+
fl.Field().Set(reflect.New(fl.Field().Type().Elem()))
76129
}
77130
return nil
78131
}

Diff for: modifiers/multi_test.go

+196
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,202 @@ import (
88
. "github.com/go-playground/assert/v2"
99
)
1010

11+
func TestDefaultSetSpecialTypes(t *testing.T) {
12+
conform := New()
13+
14+
tests := []struct {
15+
name string
16+
field interface{}
17+
tags string
18+
vf func(field interface{})
19+
expectError bool
20+
}{
21+
{
22+
name: "default map",
23+
field: (map[string]struct{})(nil),
24+
tags: "default",
25+
vf: func(field interface{}) {
26+
m := field.(map[string]struct{})
27+
Equal(t, len(m), 0)
28+
},
29+
},
30+
{
31+
name: "default map with size",
32+
field: (map[string]struct{})(nil),
33+
tags: "default=5",
34+
vf: func(field interface{}) {
35+
m := field.(map[string]struct{})
36+
Equal(t, len(m), 0)
37+
},
38+
},
39+
{
40+
name: "set map with size",
41+
field: (map[string]struct{})(nil),
42+
tags: "set=5",
43+
vf: func(field interface{}) {
44+
m := field.(map[string]struct{})
45+
Equal(t, len(m), 0)
46+
},
47+
},
48+
{
49+
name: "default slice",
50+
field: ([]string)(nil),
51+
tags: "default",
52+
vf: func(field interface{}) {
53+
m := field.([]string)
54+
Equal(t, len(m), 0)
55+
Equal(t, cap(m), 0)
56+
},
57+
},
58+
{
59+
name: "default slice with capacity",
60+
field: ([]string)(nil),
61+
tags: "default=5",
62+
vf: func(field interface{}) {
63+
m := field.([]string)
64+
Equal(t, len(m), 0)
65+
Equal(t, cap(m), 5)
66+
},
67+
},
68+
{
69+
name: "set slice",
70+
field: ([]string)(nil),
71+
tags: "set",
72+
vf: func(field interface{}) {
73+
m := field.([]string)
74+
Equal(t, len(m), 0)
75+
Equal(t, cap(m), 0)
76+
},
77+
},
78+
{
79+
name: "set slice with capacity",
80+
field: ([]string)(nil),
81+
tags: "set=5",
82+
vf: func(field interface{}) {
83+
m := field.([]string)
84+
Equal(t, len(m), 0)
85+
Equal(t, cap(m), 5)
86+
},
87+
},
88+
{
89+
name: "default chan",
90+
field: (chan struct{})(nil),
91+
tags: "default",
92+
vf: func(field interface{}) {
93+
m := field.(chan struct{})
94+
Equal(t, len(m), 0)
95+
Equal(t, cap(m), 0)
96+
},
97+
},
98+
{
99+
name: "default chan with buffer",
100+
field: (chan struct{})(nil),
101+
tags: "default=5",
102+
vf: func(field interface{}) {
103+
m := field.(chan struct{})
104+
Equal(t, len(m), 0)
105+
Equal(t, cap(m), 5)
106+
},
107+
},
108+
{
109+
name: "default time.Time",
110+
field: time.Time{},
111+
tags: "default",
112+
vf: func(field interface{}) {
113+
m := field.(time.Time)
114+
Equal(t, m.Location(), time.Local)
115+
},
116+
},
117+
{
118+
name: "default time.Time utc",
119+
field: time.Time{},
120+
tags: "default=utc",
121+
vf: func(field interface{}) {
122+
m := field.(time.Time)
123+
Equal(t, m.Location(), time.UTC)
124+
},
125+
},
126+
{
127+
name: "default time.Time to value",
128+
field: time.Time{},
129+
tags: "default=2023-05-28T15:50:31Z",
130+
vf: func(field interface{}) {
131+
m := field.(time.Time)
132+
Equal(t, m.Location(), time.UTC)
133+
134+
tm, err := time.Parse(time.RFC3339Nano, "2023-05-28T15:50:31Z")
135+
Equal(t, err, nil)
136+
Equal(t, tm.Equal(m), true)
137+
138+
},
139+
},
140+
{
141+
name: "set time.Time",
142+
field: time.Time{},
143+
tags: "set",
144+
vf: func(field interface{}) {
145+
m := field.(time.Time)
146+
Equal(t, m.Location(), time.Local)
147+
},
148+
},
149+
{
150+
name: "set time.Time utc",
151+
field: time.Time{},
152+
tags: "set=utc",
153+
vf: func(field interface{}) {
154+
m := field.(time.Time)
155+
Equal(t, m.Location(), time.UTC)
156+
},
157+
},
158+
{
159+
name: "set time.Time to value",
160+
field: time.Time{},
161+
tags: "set=2023-05-28T15:50:31Z",
162+
vf: func(field interface{}) {
163+
m := field.(time.Time)
164+
Equal(t, m.Location(), time.UTC)
165+
166+
tm, err := time.Parse(time.RFC3339Nano, "2023-05-28T15:50:31Z")
167+
Equal(t, err, nil)
168+
Equal(t, tm.Equal(m), true)
169+
170+
},
171+
},
172+
{
173+
name: "default pointer to slice",
174+
field: (*[]string)(nil),
175+
tags: "default",
176+
vf: func(field interface{}) {
177+
m := field.([]string)
178+
Equal(t, len(m), 0)
179+
},
180+
},
181+
{
182+
name: "set pointer to slice",
183+
field: (*[]string)(nil),
184+
tags: "set",
185+
vf: func(field interface{}) {
186+
m := field.([]string)
187+
Equal(t, len(m), 0)
188+
},
189+
},
190+
}
191+
192+
for _, tc := range tests {
193+
tc := tc
194+
t.Run(tc.name, func(t *testing.T) {
195+
t.Parallel()
196+
err := conform.Field(context.Background(), &tc.field, tc.tags)
197+
if tc.expectError {
198+
NotEqual(t, err, nil)
199+
return
200+
}
201+
Equal(t, err, nil)
202+
tc.vf(tc.field)
203+
})
204+
}
205+
}
206+
11207
func TestSet(t *testing.T) {
12208

13209
type State int

Diff for: mold.go

+10
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ func (t *Transformer) setByField(ctx context.Context, orig reflect.Value, ct *cT
239239
err = t.setByIterable(ctx, current, ct)
240240
case reflect.Map:
241241
err = t.setByMap(ctx, current, ct)
242+
case reflect.Ptr:
243+
innerKind := current.Type().Elem().Kind()
244+
if innerKind == reflect.Slice || innerKind == reflect.Map {
245+
// is a nil pointer to a slice or map, nothing to do.
246+
return nil
247+
}
248+
// not a valid use of the dive tag
249+
fallthrough
242250
default:
243251
err = ErrInvalidDive
244252
}
@@ -267,6 +275,8 @@ func (t *Transformer) setByField(ctx context.Context, orig reflect.Value, ct *cT
267275
}); err != nil {
268276
return
269277
}
278+
// value could have been changed or reassigned
279+
current, kind = t.extractType(current)
270280
}
271281
ct = ct.next
272282
}

0 commit comments

Comments
 (0)