dff36a18 by astaxie

Merge pull request #101 from miraclesu/valid

Valid
2 parents f46388fa fb78d83e
1 validation
2 ==============
3
4 validation is a form validation for a data validation and error collecting using Go.
5
6 ## Installation and tests
7
8 Install:
9
10 go get github.com/astaxie/beego/validation
11
12 Test:
13
14 go test github.com/astaxie/beego/validation
15
16 ## Example
17
18 Direct Use:
19
20 import (
21 "github.com/astaxie/beego/validation"
22 "log"
23 )
24
25 type User struct {
26 Name string
27 Age int
28 }
29
30 func main() {
31 u := User{"man", 40}
32 valid := validation.Validation{}
33 valid.Required(u.Name, "name")
34 valid.MaxSize(u.Name, 15, "nameMax")
35 valid.Range(u.Age, 0, 140, "age")
36 if valid.HasErrors {
37 // validation does not pass
38 // print invalid message
39 for _, err := range valid.Errors {
40 log.Println(err.Key, err.Message)
41 }
42 }
43 // or use like this
44 if v := valid.Max(u.Age, 140); !v.Ok {
45 log.Println(v.Error.Key, v.Error.Message)
46 }
47 }
48
49 Struct Tag Use:
50
51 import (
52 "github.com/astaxie/beego/validation"
53 )
54
55 // validation function follow with "valid" tag
56 // functions divide with ";"
57 // parameters in parentheses "()" and divide with ","
58 type user struct {
59 Id int
60 Name string `valid:"Required"`
61 Age int `valid:"Required;Range(1, 140)"`
62 }
63
64 func main() {
65 valid := Validation{}
66 u := user{Name: "test", Age: 40}
67 b, err := valid.Valid(u)
68 if err != nil {
69 // handle error
70 }
71 if !b {
72 // validation does not pass
73 // blabla...
74 }
75 }
76
77 Struct Tag Functions:
78
79 Required
80 Min(min int)
81 Max(max int)
82 Range(min, max int)
83 MinSize(min int)
84 MaxSize(max int)
85 Length(length int)
86 Alpha
87 Numeric
88 AlphaNumeric
89 Match(regexp string) // does not support yet
90 NoMatch(regexp string) // does not support yet
91 AlphaDash
92 Email
93 IP
94 Base64
95
96
97 ## LICENSE
98
99 BSD License http://creativecommons.org/licenses/BSD/
1 package validation
2
3 import (
4 "fmt"
5 "reflect"
6 "regexp"
7 "strconv"
8 "strings"
9 )
10
11 const (
12 VALIDTAG = "valid"
13 )
14
15 var (
16 // key: function name
17 // value: the number of parameters
18 funcs = make(Funcs)
19
20 // doesn't belong to validation functions
21 unFuncs = map[string]bool{
22 "Clear": true,
23 "HasErrors": true,
24 "ErrorMap": true,
25 "Error": true,
26 "apply": true,
27 "Check": true,
28 "Valid": true,
29 }
30 )
31
32 func init() {
33 v := &Validation{}
34 t := reflect.TypeOf(v)
35 for i := 0; i < t.NumMethod(); i++ {
36 m := t.Method(i)
37 if !unFuncs[m.Name] {
38 funcs[m.Name] = m.Func
39 }
40 }
41 }
42
43 type ValidFunc struct {
44 Name string
45 Params []interface{}
46 }
47
48 type Funcs map[string]reflect.Value
49
50 func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) {
51 defer func() {
52 if r := recover(); r != nil {
53 err = r.(error)
54 }
55 }()
56 if _, ok := f[name]; !ok {
57 err = fmt.Errorf("%s does not exist", name)
58 return
59 }
60 if len(params) != f[name].Type().NumIn() {
61 err = fmt.Errorf("The number of params is not adapted")
62 return
63 }
64 in := make([]reflect.Value, len(params))
65 for k, param := range params {
66 in[k] = reflect.ValueOf(param)
67 }
68 result = f[name].Call(in)
69 return
70 }
71
72 func isStruct(t reflect.Type) bool {
73 return t.Kind() == reflect.Struct
74 }
75
76 func isStructPtr(t reflect.Type) bool {
77 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
78 }
79
80 func getValidFuncs(f reflect.StructField) (vfs []ValidFunc, err error) {
81 tag := f.Tag.Get(VALIDTAG)
82 if len(tag) == 0 {
83 return
84 }
85 fs := strings.Split(tag, ";")
86 for _, vfunc := range fs {
87 var vf ValidFunc
88 vf, err = parseFunc(vfunc)
89 if err != nil {
90 return
91 }
92 vfs = append(vfs, vf)
93 }
94 return
95 }
96
97 func parseFunc(vfunc string) (v ValidFunc, err error) {
98 defer func() {
99 if r := recover(); r != nil {
100 err = r.(error)
101 }
102 }()
103
104 vfunc = strings.TrimSpace(vfunc)
105 start := strings.Index(vfunc, "(")
106 var num int
107
108 // doesn't need parameter valid function
109 if start == -1 {
110 if num, err = numIn(vfunc); err != nil {
111 return
112 }
113 if num != 0 {
114 err = fmt.Errorf("%s require %d parameters", vfunc, num)
115 return
116 }
117 v = ValidFunc{vfunc, []interface{}{vfunc}}
118 return
119 }
120
121 end := strings.Index(vfunc, ")")
122 if end == -1 {
123 err = fmt.Errorf("invalid valid function")
124 return
125 }
126
127 name := strings.TrimSpace(vfunc[:start])
128 if num, err = numIn(name); err != nil {
129 return
130 }
131
132 params := strings.Split(vfunc[start+1:end], ",")
133 // the num of param must be equal
134 if num != len(params) {
135 err = fmt.Errorf("%s require %d parameters", name, num)
136 return
137 }
138
139 tParams, err := trim(name, params)
140 if err != nil {
141 return
142 }
143 v = ValidFunc{name, tParams}
144 return
145 }
146
147 func numIn(name string) (num int, err error) {
148 fn, ok := funcs[name]
149 if !ok {
150 err = fmt.Errorf("doesn't exsits %s valid function", name)
151 return
152 }
153 // sub *Validation obj and key
154 num = fn.Type().NumIn() - 3
155 return
156 }
157
158 func trim(name string, s []string) (ts []interface{}, err error) {
159 ts = make([]interface{}, len(s), len(s)+1)
160 fn, ok := funcs[name]
161 if !ok {
162 err = fmt.Errorf("doesn't exsits %s valid function", name)
163 return
164 }
165 for i := 0; i < len(s); i++ {
166 var param interface{}
167 // skip *Validation and obj params
168 if param, err = magic(fn.Type().In(i+2), strings.TrimSpace(s[i])); err != nil {
169 return
170 }
171 ts[i] = param
172 }
173 ts = append(ts, name)
174 return
175 }
176
177 // modify the parameters's type to adapt the function input parameters' type
178 func magic(t reflect.Type, s string) (i interface{}, err error) {
179 switch t.Kind() {
180 case reflect.Int:
181 i, err = strconv.Atoi(s)
182 case reflect.String:
183 i = s
184 case reflect.Ptr:
185 if t.Elem().String() != "regexp.Regexp" {
186 err = fmt.Errorf("does not support %s", t.Elem().String())
187 return
188 }
189 i, err = regexp.Compile(s)
190 default:
191 err = fmt.Errorf("does not support %s", t.Kind().String())
192 }
193 return
194 }
195
196 func mergeParam(v *Validation, obj interface{}, params []interface{}) []interface{} {
197 return append([]interface{}{v, obj}, params...)
198 }
1 package validation
2
3 import (
4 "reflect"
5 "testing"
6 )
7
8 type user struct {
9 Id int
10 Tag string `valid:"Maxx(aa)"`
11 Name string `valid:"Required"`
12 Age int `valid:"Required;Range(1, 140)"`
13 }
14
15 func TestGetValidFuncs(t *testing.T) {
16 u := user{Name: "test", Age: 1}
17 tf := reflect.TypeOf(u)
18 var vfs []ValidFunc
19 var err error
20
21 f, _ := tf.FieldByName("Id")
22 if vfs, err = getValidFuncs(f); err != nil {
23 t.Fatal(err)
24 }
25 if len(vfs) != 0 {
26 t.Fatal("should get none ValidFunc")
27 }
28
29 f, _ = tf.FieldByName("Tag")
30 if vfs, err = getValidFuncs(f); err.Error() != "doesn't exsits Maxx valid function" {
31 t.Fatal(err)
32 }
33
34 f, _ = tf.FieldByName("Name")
35 if vfs, err = getValidFuncs(f); err != nil {
36 t.Fatal(err)
37 }
38 if len(vfs) != 1 {
39 t.Fatal("should get 1 ValidFunc")
40 }
41 if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 {
42 t.Error("Required funcs should be got")
43 }
44
45 f, _ = tf.FieldByName("Age")
46 if vfs, err = getValidFuncs(f); err != nil {
47 t.Fatal(err)
48 }
49 if len(vfs) != 2 {
50 t.Fatal("should get 2 ValidFunc")
51 }
52 if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 {
53 t.Error("Required funcs should be got")
54 }
55 if vfs[1].Name != "Range" && len(vfs[1].Params) != 2 {
56 t.Error("Range funcs should be got")
57 }
58 }
59
60 func TestCall(t *testing.T) {
61 u := user{Name: "test", Age: 180}
62 tf := reflect.TypeOf(u)
63 var vfs []ValidFunc
64 var err error
65 f, _ := tf.FieldByName("Age")
66 if vfs, err = getValidFuncs(f); err != nil {
67 t.Fatal(err)
68 }
69 valid := &Validation{}
70 vfs[1].Params = append([]interface{}{valid, u.Age}, vfs[1].Params...)
71 if _, err = funcs.Call(vfs[1].Name, vfs[1].Params...); err != nil {
72 t.Fatal(err)
73 }
74 if len(valid.Errors) != 1 {
75 t.Error("age out of range should be has an error")
76 }
77 }
...@@ -2,6 +2,7 @@ package validation ...@@ -2,6 +2,7 @@ package validation
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "reflect"
5 "regexp" 6 "regexp"
6 ) 7 )
7 8
...@@ -175,3 +176,32 @@ func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResu ...@@ -175,3 +176,32 @@ func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResu
175 } 176 }
176 return result 177 return result
177 } 178 }
179
180 // the obj parameter must be a struct or a struct pointer
181 func (v *Validation) Valid(obj interface{}) (b bool, err error) {
182 objT := reflect.TypeOf(obj)
183 objV := reflect.ValueOf(obj)
184 switch {
185 case isStruct(objT):
186 case isStructPtr(objT):
187 objT = objT.Elem()
188 objV = objV.Elem()
189 default:
190 err = fmt.Errorf("%v must be a struct or a struct pointer", obj)
191 return
192 }
193
194 for i := 0; i < objT.NumField(); i++ {
195 var vfs []ValidFunc
196 if vfs, err = getValidFuncs(objT.Field(i)); err != nil {
197 return
198 }
199 for _, vf := range vfs {
200 if _, err = funcs.Call(vf.Name,
201 mergeParam(v, objV.Field(i).Interface(), vf.Params)...); err != nil {
202 return
203 }
204 }
205 }
206 return !v.HasErrors(), nil
207 }
......
...@@ -31,7 +31,7 @@ func TestRequired(t *testing.T) { ...@@ -31,7 +31,7 @@ func TestRequired(t *testing.T) {
31 t.Error("empty slice should be false") 31 t.Error("empty slice should be false")
32 } 32 }
33 if !valid.Required([]interface{}{"ok"}, "slice").Ok { 33 if !valid.Required([]interface{}{"ok"}, "slice").Ok {
34 t.Error("slice should be equal true") 34 t.Error("slice should be true")
35 } 35 }
36 } 36 }
37 37
...@@ -217,3 +217,30 @@ func TestBase64(t *testing.T) { ...@@ -217,3 +217,30 @@ func TestBase64(t *testing.T) {
217 t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true") 217 t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true")
218 } 218 }
219 } 219 }
220
221 func TestValid(t *testing.T) {
222 type user struct {
223 Id int
224 Name string `valid:"Required"`
225 Age int `valid:"Required;Range(1, 140)"`
226 }
227 valid := Validation{}
228
229 u := user{Name: "test", Age: 40}
230 b, err := valid.Valid(u)
231 if err != nil {
232 t.Fatal(err)
233 }
234 if !b {
235 t.Error("validation should be passed")
236 }
237
238 uptr := &user{Name: "test", Age: 180}
239 b, err = valid.Valid(uptr)
240 if err != nil {
241 t.Fatal(err)
242 }
243 if b {
244 t.Error("validation should not be passed")
245 }
246 }
......
...@@ -63,7 +63,7 @@ func (m Min) IsSatisfied(obj interface{}) bool { ...@@ -63,7 +63,7 @@ func (m Min) IsSatisfied(obj interface{}) bool {
63 } 63 }
64 64
65 func (m Min) DefaultMessage() string { 65 func (m Min) DefaultMessage() string {
66 return fmt.Sprintln("Minimum is", m.Min) 66 return fmt.Sprint("Minimum is ", m.Min)
67 } 67 }
68 68
69 func (m Min) GetKey() string { 69 func (m Min) GetKey() string {
...@@ -84,7 +84,7 @@ func (m Max) IsSatisfied(obj interface{}) bool { ...@@ -84,7 +84,7 @@ func (m Max) IsSatisfied(obj interface{}) bool {
84 } 84 }
85 85
86 func (m Max) DefaultMessage() string { 86 func (m Max) DefaultMessage() string {
87 return fmt.Sprintln("Maximum is", m.Max) 87 return fmt.Sprint("Maximum is ", m.Max)
88 } 88 }
89 89
90 func (m Max) GetKey() string { 90 func (m Max) GetKey() string {
...@@ -103,7 +103,7 @@ func (r Range) IsSatisfied(obj interface{}) bool { ...@@ -103,7 +103,7 @@ func (r Range) IsSatisfied(obj interface{}) bool {
103 } 103 }
104 104
105 func (r Range) DefaultMessage() string { 105 func (r Range) DefaultMessage() string {
106 return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max) 106 return fmt.Sprint("Range is ", r.Min.Min, " to ", r.Max.Max)
107 } 107 }
108 108
109 func (r Range) GetKey() string { 109 func (r Range) GetKey() string {
...@@ -128,7 +128,7 @@ func (m MinSize) IsSatisfied(obj interface{}) bool { ...@@ -128,7 +128,7 @@ func (m MinSize) IsSatisfied(obj interface{}) bool {
128 } 128 }
129 129
130 func (m MinSize) DefaultMessage() string { 130 func (m MinSize) DefaultMessage() string {
131 return fmt.Sprintln("Minimum size is", m.Min) 131 return fmt.Sprint("Minimum size is ", m.Min)
132 } 132 }
133 133
134 func (m MinSize) GetKey() string { 134 func (m MinSize) GetKey() string {
...@@ -153,7 +153,7 @@ func (m MaxSize) IsSatisfied(obj interface{}) bool { ...@@ -153,7 +153,7 @@ func (m MaxSize) IsSatisfied(obj interface{}) bool {
153 } 153 }
154 154
155 func (m MaxSize) DefaultMessage() string { 155 func (m MaxSize) DefaultMessage() string {
156 return fmt.Sprintln("Maximum size is", m.Max) 156 return fmt.Sprint("Maximum size is ", m.Max)
157 } 157 }
158 158
159 func (m MaxSize) GetKey() string { 159 func (m MaxSize) GetKey() string {
...@@ -178,7 +178,7 @@ func (l Length) IsSatisfied(obj interface{}) bool { ...@@ -178,7 +178,7 @@ func (l Length) IsSatisfied(obj interface{}) bool {
178 } 178 }
179 179
180 func (l Length) DefaultMessage() string { 180 func (l Length) DefaultMessage() string {
181 return fmt.Sprintln("Required length is", l.N) 181 return fmt.Sprint("Required length is ", l.N)
182 } 182 }
183 183
184 func (l Length) GetKey() string { 184 func (l Length) GetKey() string {
...@@ -202,7 +202,7 @@ func (a Alpha) IsSatisfied(obj interface{}) bool { ...@@ -202,7 +202,7 @@ func (a Alpha) IsSatisfied(obj interface{}) bool {
202 } 202 }
203 203
204 func (a Alpha) DefaultMessage() string { 204 func (a Alpha) DefaultMessage() string {
205 return fmt.Sprintln("Must be valid alpha characters") 205 return fmt.Sprint("Must be valid alpha characters")
206 } 206 }
207 207
208 func (a Alpha) GetKey() string { 208 func (a Alpha) GetKey() string {
...@@ -226,7 +226,7 @@ func (n Numeric) IsSatisfied(obj interface{}) bool { ...@@ -226,7 +226,7 @@ func (n Numeric) IsSatisfied(obj interface{}) bool {
226 } 226 }
227 227
228 func (n Numeric) DefaultMessage() string { 228 func (n Numeric) DefaultMessage() string {
229 return fmt.Sprintln("Must be valid numeric characters") 229 return fmt.Sprint("Must be valid numeric characters")
230 } 230 }
231 231
232 func (n Numeric) GetKey() string { 232 func (n Numeric) GetKey() string {
...@@ -250,7 +250,7 @@ func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { ...@@ -250,7 +250,7 @@ func (a AlphaNumeric) IsSatisfied(obj interface{}) bool {
250 } 250 }
251 251
252 func (a AlphaNumeric) DefaultMessage() string { 252 func (a AlphaNumeric) DefaultMessage() string {
253 return fmt.Sprintln("Must be valid alpha or numeric characters") 253 return fmt.Sprint("Must be valid alpha or numeric characters")
254 } 254 }
255 255
256 func (a AlphaNumeric) GetKey() string { 256 func (a AlphaNumeric) GetKey() string {
...@@ -269,7 +269,7 @@ func (m Match) IsSatisfied(obj interface{}) bool { ...@@ -269,7 +269,7 @@ func (m Match) IsSatisfied(obj interface{}) bool {
269 } 269 }
270 270
271 func (m Match) DefaultMessage() string { 271 func (m Match) DefaultMessage() string {
272 return fmt.Sprintln("Must match", m.Regexp) 272 return fmt.Sprint("Must match ", m.Regexp)
273 } 273 }
274 274
275 func (m Match) GetKey() string { 275 func (m Match) GetKey() string {
...@@ -287,7 +287,7 @@ func (n NoMatch) IsSatisfied(obj interface{}) bool { ...@@ -287,7 +287,7 @@ func (n NoMatch) IsSatisfied(obj interface{}) bool {
287 } 287 }
288 288
289 func (n NoMatch) DefaultMessage() string { 289 func (n NoMatch) DefaultMessage() string {
290 return fmt.Sprintln("Must not match", n.Regexp) 290 return fmt.Sprint("Must not match ", n.Regexp)
291 } 291 }
292 292
293 func (n NoMatch) GetKey() string { 293 func (n NoMatch) GetKey() string {
...@@ -302,7 +302,7 @@ type AlphaDash struct { ...@@ -302,7 +302,7 @@ type AlphaDash struct {
302 } 302 }
303 303
304 func (a AlphaDash) DefaultMessage() string { 304 func (a AlphaDash) DefaultMessage() string {
305 return fmt.Sprintln("Must be valid alpha or numeric or dash(-_) characters") 305 return fmt.Sprint("Must be valid alpha or numeric or dash(-_) characters")
306 } 306 }
307 307
308 func (a AlphaDash) GetKey() string { 308 func (a AlphaDash) GetKey() string {
...@@ -317,7 +317,7 @@ type Email struct { ...@@ -317,7 +317,7 @@ type Email struct {
317 } 317 }
318 318
319 func (e Email) DefaultMessage() string { 319 func (e Email) DefaultMessage() string {
320 return fmt.Sprintln("Must be a valid email address") 320 return fmt.Sprint("Must be a valid email address")
321 } 321 }
322 322
323 func (e Email) GetKey() string { 323 func (e Email) GetKey() string {
...@@ -332,7 +332,7 @@ type IP struct { ...@@ -332,7 +332,7 @@ type IP struct {
332 } 332 }
333 333
334 func (i IP) DefaultMessage() string { 334 func (i IP) DefaultMessage() string {
335 return fmt.Sprintln("Must be a valid ip address") 335 return fmt.Sprint("Must be a valid ip address")
336 } 336 }
337 337
338 func (i IP) GetKey() string { 338 func (i IP) GetKey() string {
...@@ -347,7 +347,7 @@ type Base64 struct { ...@@ -347,7 +347,7 @@ type Base64 struct {
347 } 347 }
348 348
349 func (b Base64) DefaultMessage() string { 349 func (b Base64) DefaultMessage() string {
350 return fmt.Sprintln("Must be valid base64 characters") 350 return fmt.Sprint("Must be valid base64 characters")
351 } 351 }
352 352
353 func (b Base64) GetKey() string { 353 func (b Base64) GetKey() string {
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!