Skip to content

Commit

Permalink
feat: enable slices of maps on structs
Browse files Browse the repository at this point in the history
Right now, the system only allows to  use `tags.*` for slices, but if
the slice is a complex type(struct or map) there is no way to validate.

This commits adds a way to test inside slice content if the value is a
map:

```
var data = map[string]interface{}{
  "user": "foo",
  "properties": []map[string]string{{
    "phone": "12001",
    "alias": "foobar",
  }},
}

v := validate.Map(data)

v.ConfigRules(map[string]string{
  "properties.*.admin": "bool",
  "properties.*.alias": "minlen:4",
})
```

Added tests inside a current test, I thought that makes sense to have in
there.

Signed-off-by: Eloy Coto <eloy.coto@acalustra.com>
  • Loading branch information
eloycoto committed Oct 6, 2022
1 parent aea09bc commit 6815f54
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 8 deletions.
17 changes: 16 additions & 1 deletion issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -766,15 +766,30 @@ func TestIssues_120(t *testing.T) {
func TestIssue_124(t *testing.T) {
m := map[string]interface{}{
"names": []string{"John", "Jane", "abc"},
"address": []map[string]string{
{"number": "1b", "country": "en"},
{"number": "1", "country": "cz"},
},
}

v := validate.Map(m)
v.StopOnError = false

// simple slices
v.StringRule("names", "required|array|minLen:1")
v.StringRule("names.*", "required|string|min_len:4")

// slices of maps
v.StringRule("address.*.number", "required|int")
v.StringRule("address.*.country", "required|in:es,us")

assert.False(t, v.Validate())
assert.Error(t, v.Errors.ErrOrNil())
assert.Equal(t, "names.* min length is 4", v.Errors.One())

assert.Equal(t, len(v.Errors.All()), 3)
assert.Equal(t, "names.* min length is 4", v.Errors.Field("names.*")["min_len"])
assert.Equal(t, "address.*.number value must be an integer", v.Errors.Field("address.*.number")["int"])
assert.Equal(t, "address.*.country value must be in the enum [es us]", v.Errors.Field("address.*.country")["in"])
// dump.Println(v.Errors)

// TODO how to use on struct.
Expand Down
3 changes: 2 additions & 1 deletion validating.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ func (r *Rule) valueValidate(field, name string, val interface{}, v *Validation)
valKind := rftVal.Kind()

// feat: support check sub element in a slice list. eg: field=names.*
if valKind == reflect.Slice && strings.HasSuffix(field, ".*") {
hasSliceSuffix := len(strings.Split(field, ".*")) > 1
if valKind == reflect.Slice && hasSliceSuffix {
var subVal interface{}
for i := 0; i < rftVal.Len(); i++ {
subRv := rftVal.Index(i)
Expand Down
11 changes: 5 additions & 6 deletions validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,10 @@ func (v *Validation) tryGet(key string) (val interface{}, exist, zero bool) {
if v.data == nil {
return
}

vals := strings.Split(key, ".*")
if len(vals) > 1 {
key = vals[0]
}
// if end withs: .*, return the parent value
key = strings.TrimSuffix(key, ".*")

Expand Down Expand Up @@ -503,11 +506,7 @@ func (v *Validation) Set(field string, val interface{}) error {
func (v *Validation) updateValue(field string, val interface{}) (interface{}, error) {
// data source is struct
if v.data.Type() == sourceStruct {
// if end withs: .*, trim suffix
if strings.HasSuffix(field, ".*") {
field = field[0 : len(field)-2]
}
return v.data.Set(field, val)
return v.data.Set(strings.TrimSuffix(field, ".*"), val)
}

// TODO dont update value for Form and Map data source
Expand Down

0 comments on commit 6815f54

Please sign in to comment.