-
Notifications
You must be signed in to change notification settings - Fork 0
/
v.go
166 lines (141 loc) · 3.88 KB
/
v.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package v
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/ladydascalie/v/validators"
)
const (
tagname = "v"
jsontag = "json"
// common tags
required = "required"
)
// Set a new validator into the custom func map
func Set(tag string, validator validators.Validator) {
c := validators.GetFuncMap()
c.Set(tag, validator)
}
// Get a validator from the custom func map
func Get(tag string) (validator validators.Validator, ok bool) {
c := validators.GetFuncMap()
return c.Get(tag)
}
// Struct takes in an interface, which must be a struct
// all validation is ran based on the provided tags.
func Struct(structure interface{}) error {
// nothing to see here
if structure == nil {
return nil
}
// ensure we're ok even if passed a pointer
v := reflect.Indirect(reflect.ValueOf(structure))
if v.Kind() != reflect.Struct {
return errors.New("only structs may be passed to this method")
}
t := v.Type() // get the struct type
for i := 0; i < t.NumField(); i++ {
field := t.Field(i) // prepare the field
value := v.Field(i) // prepare the field value
// retrieve the underlying value if possible
if value.Kind() == reflect.Ptr || value.Kind() == reflect.Interface {
value = value.Elem()
}
// recurse if this is an embedded struct
if value.Kind() == reflect.Struct && field.PkgPath == "" {
// only exported fields should do this
if err := Struct(value.Interface()); err != nil {
return err
}
}
// get all the v tags
tags := field.Tag.Get(tagname)
// get the json tags
jtag := field.Tag.Get(jsontag)
// split the v tags on comma
vtags := strings.Split(tags, ",")
// validation errors collection
var vErrors validationErorrs
// range over the tags
for _, vtag := range vtags {
if err := handleValidationTag(vtag, jtag, field, value, structure); err != nil {
vErrors = append(vErrors, err)
}
}
if len(vErrors) != 0 {
return vErrors.Error()
}
}
return nil
}
func handleValidationTag(vtag, jtag string, field reflect.StructField, value reflect.Value, structure interface{}) (err error) {
// sanitize the tag. when multiple tags are used
// some leading/trailing spaces may be left
vtag = strings.TrimSpace(vtag)
// guard against unexported fields
// or simply missing or invalid tags
if field.PkgPath != "" || vtag == "" {
return
}
// is the field required but invalid?
// this will trigger for instance on a *string
// which has not been initialized.
if !value.IsValid() && vtag == required {
return ErrorRequired{
Field: field.Name,
JSONName: jtag,
}
}
// Our field is valid, and we can interface without panic
// we are ready to send it to the validator methods
if value.IsValid() && value.CanInterface() {
if err = validate(vtag, value.Interface(), structure); err != nil {
return ErrorValidation{
Name: field.Name,
JSONName: jtag,
Err: err,
}
}
}
return
}
func validate(tag string, value, structure interface{}) error {
vtag := newValidationTag(tag)
if vtag == nil {
return fmt.Errorf("v cannot parse struct tag <%v> please refer to the format rules", tag)
}
// first check for custom functions
if vtag.Name == "func" {
fn, ok := validators.CustomFuncMap.Get(vtag.Args)
if ok {
return fn(vtag.Args, value, structure)
}
return fmt.Errorf("custom validator %s did not match any available function", vtag.Args)
}
// run through the func map and see if there's a match
for name, method := range validators.FuncMap {
if vtag.Name == name {
return method(vtag.Args, value)
}
}
return fmt.Errorf("could not parse validation tag: %s", vtag.Name)
}
type validationTag struct {
Name string
Args string
}
func newValidationTag(tag string) *validationTag {
var vtag validationTag
parts := strings.SplitN(tag, ":", -1)
switch len(parts) {
case 1:
vtag.Name = parts[0]
case 2:
vtag.Name = parts[0]
vtag.Args = parts[1]
default:
return nil
}
return &vtag
}