a242f61b by astaxie

Merge pull request #100 from miraclesu/valid

Valid
2 parents ec7324e9 d19de30d
1 package validation
2
3 import (
4 "fmt"
5 "regexp"
6 )
7
8 type ValidationError struct {
9 Message, Key string
10 }
11
12 // Returns the Message.
13 func (e *ValidationError) String() string {
14 if e == nil {
15 return ""
16 }
17 return e.Message
18 }
19
20 // A Validation context manages data validation and error messages.
21 type Validation struct {
22 Errors []*ValidationError
23 }
24
25 func (v *Validation) Clear() {
26 v.Errors = []*ValidationError{}
27 }
28
29 func (v *Validation) HasErrors() bool {
30 return len(v.Errors) > 0
31 }
32
33 // Return the errors mapped by key.
34 // If there are multiple validation errors associated with a single key, the
35 // first one "wins". (Typically the first validation will be the more basic).
36 func (v *Validation) ErrorMap() map[string]*ValidationError {
37 m := map[string]*ValidationError{}
38 for _, e := range v.Errors {
39 if _, ok := m[e.Key]; !ok {
40 m[e.Key] = e
41 }
42 }
43 return m
44 }
45
46 // Add an error to the validation context.
47 func (v *Validation) Error(message string, args ...interface{}) *ValidationResult {
48 result := (&ValidationResult{
49 Ok: false,
50 Error: &ValidationError{},
51 }).Message(message, args...)
52 v.Errors = append(v.Errors, result.Error)
53 return result
54 }
55
56 // A ValidationResult is returned from every validation method.
57 // It provides an indication of success, and a pointer to the Error (if any).
58 type ValidationResult struct {
59 Error *ValidationError
60 Ok bool
61 }
62
63 func (r *ValidationResult) Key(key string) *ValidationResult {
64 if r.Error != nil {
65 r.Error.Key = key
66 }
67 return r
68 }
69
70 func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult {
71 if r.Error != nil {
72 if len(args) == 0 {
73 r.Error.Message = message
74 } else {
75 r.Error.Message = fmt.Sprintf(message, args...)
76 }
77 }
78 return r
79 }
80
81 // Test that the argument is non-nil and non-empty (if string or list)
82 func (v *Validation) Required(obj interface{}, key string) *ValidationResult {
83 return v.apply(Required{key}, obj)
84 }
85
86 func (v *Validation) Min(n int, min int, key string) *ValidationResult {
87 return v.apply(Min{min, key}, n)
88 }
89
90 func (v *Validation) Max(n int, max int, key string) *ValidationResult {
91 return v.apply(Max{max, key}, n)
92 }
93
94 func (v *Validation) Range(n, min, max int, key string) *ValidationResult {
95 return v.apply(Range{Min{Min: min}, Max{Max: max}, key}, n)
96 }
97
98 func (v *Validation) MinSize(obj interface{}, min int, key string) *ValidationResult {
99 return v.apply(MinSize{min, key}, obj)
100 }
101
102 func (v *Validation) MaxSize(obj interface{}, max int, key string) *ValidationResult {
103 return v.apply(MaxSize{max, key}, obj)
104 }
105
106 func (v *Validation) Length(obj interface{}, n int, key string) *ValidationResult {
107 return v.apply(Length{n, key}, obj)
108 }
109
110 func (v *Validation) Alpha(obj interface{}, key string) *ValidationResult {
111 return v.apply(Alpha{key}, obj)
112 }
113
114 func (v *Validation) Numeric(obj interface{}, key string) *ValidationResult {
115 return v.apply(Numeric{key}, obj)
116 }
117
118 func (v *Validation) AlphaNumeric(obj interface{}, key string) *ValidationResult {
119 return v.apply(AlphaNumeric{key}, obj)
120 }
121
122 func (v *Validation) Match(str string, regex *regexp.Regexp, key string) *ValidationResult {
123 return v.apply(Match{regex, key}, str)
124 }
125
126 func (v *Validation) NoMatch(str string, regex *regexp.Regexp, key string) *ValidationResult {
127 return v.apply(NoMatch{Match{Regexp: regex}, key}, str)
128 }
129
130 func (v *Validation) AlphaDash(str string, key string) *ValidationResult {
131 return v.apply(AlphaDash{NoMatch{Match: Match{Regexp: alphaDashPattern}}, key}, str)
132 }
133
134 func (v *Validation) Email(str string, key string) *ValidationResult {
135 return v.apply(Email{Match{Regexp: emailPattern}, key}, str)
136 }
137
138 func (v *Validation) IP(str string, key string) *ValidationResult {
139 return v.apply(IP{Match{Regexp: ipPattern}, key}, str)
140 }
141
142 func (v *Validation) Base64(str string, key string) *ValidationResult {
143 return v.apply(Base64{Match{Regexp: base64Pattern}, key}, str)
144 }
145
146 func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult {
147 if chk.IsSatisfied(obj) {
148 return &ValidationResult{Ok: true}
149 }
150
151 // Add the error to the validation context.
152 err := &ValidationError{
153 Message: chk.DefaultMessage(),
154 Key: chk.GetKey(),
155 }
156 v.Errors = append(v.Errors, err)
157
158 // Also return it in the result.
159 return &ValidationResult{
160 Ok: false,
161 Error: err,
162 }
163 }
164
165 // Apply a group of validators to a field, in order, and return the
166 // ValidationResult from the first one that fails, or the last one that
167 // succeeds.
168 func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult {
169 var result *ValidationResult
170 for _, check := range checks {
171 result = v.apply(check, obj)
172 if !result.Ok {
173 return result
174 }
175 }
176 return result
177 }
1 package validation
2
3 import (
4 "regexp"
5 "testing"
6 "time"
7 )
8
9 func TestRequired(t *testing.T) {
10 valid := Validation{}
11
12 if valid.Required(nil, "nil").Ok {
13 t.Error("nil object should be false")
14 }
15 if valid.Required("", "string").Ok {
16 t.Error("\"'\" string should be false")
17 }
18 if !valid.Required("astaxie", "string").Ok {
19 t.Error("string should be true")
20 }
21 if valid.Required(0, "zero").Ok {
22 t.Error("Integer should not be equal 0")
23 }
24 if !valid.Required(1, "int").Ok {
25 t.Error("Integer except 0 should be true")
26 }
27 if !valid.Required(time.Now(), "time").Ok {
28 t.Error("time should be true")
29 }
30 if valid.Required([]string{}, "emptySlice").Ok {
31 t.Error("empty slice should be false")
32 }
33 if !valid.Required([]interface{}{"ok"}, "slice").Ok {
34 t.Error("slice should be equal true")
35 }
36 }
37
38 func TestMin(t *testing.T) {
39 valid := Validation{}
40
41 if valid.Min(-1, 0, "min0").Ok {
42 t.Error("-1 is less than the minimum value of 0 should be false")
43 }
44 if !valid.Min(1, 0, "min0").Ok {
45 t.Error("1 is greater or equal than the minimum value of 0 should be true")
46 }
47 }
48
49 func TestMax(t *testing.T) {
50 valid := Validation{}
51
52 if valid.Max(1, 0, "max0").Ok {
53 t.Error("1 is greater than the minimum value of 0 should be false")
54 }
55 if !valid.Max(-1, 0, "max0").Ok {
56 t.Error("-1 is less or equal than the maximum value of 0 should be true")
57 }
58 }
59
60 func TestRange(t *testing.T) {
61 valid := Validation{}
62
63 if valid.Range(-1, 0, 1, "range0_1").Ok {
64 t.Error("-1 is bettween 0 and 1 should be false")
65 }
66 if !valid.Range(1, 0, 1, "range0_1").Ok {
67 t.Error("1 is bettween 0 and 1 should be true")
68 }
69 }
70
71 func TestMinSize(t *testing.T) {
72 valid := Validation{}
73
74 if valid.MinSize("", 1, "minSize1").Ok {
75 t.Error("the length of \"\" is less than the minimum value of 1 should be false")
76 }
77 if !valid.MinSize("ok", 1, "minSize1").Ok {
78 t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true")
79 }
80 if valid.MinSize([]string{}, 1, "minSize1").Ok {
81 t.Error("the length of empty slice is less than the minimum value of 1 should be false")
82 }
83 if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok {
84 t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true")
85 }
86 }
87
88 func TestMaxSize(t *testing.T) {
89 valid := Validation{}
90
91 if valid.MaxSize("ok", 1, "maxSize1").Ok {
92 t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false")
93 }
94 if !valid.MaxSize("", 1, "maxSize1").Ok {
95 t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true")
96 }
97 if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok {
98 t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false")
99 }
100 if !valid.MaxSize([]string{}, 1, "maxSize1").Ok {
101 t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true")
102 }
103 }
104
105 func TestLength(t *testing.T) {
106 valid := Validation{}
107
108 if valid.Length("", 1, "length1").Ok {
109 t.Error("the length of \"\" must equal 1 should be false")
110 }
111 if !valid.Length("1", 1, "length1").Ok {
112 t.Error("the length of \"1\" must equal 1 should be true")
113 }
114 if valid.Length([]string{}, 1, "length1").Ok {
115 t.Error("the length of empty slice must equal 1 should be false")
116 }
117 if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok {
118 t.Error("the length of [\"ok\"] must equal 1 should be true")
119 }
120 }
121
122 func TestAlpha(t *testing.T) {
123 valid := Validation{}
124
125 if valid.Alpha("a,1-@ $", "alpha").Ok {
126 t.Error("\"a,1-@ $\" are valid alpha characters should be false")
127 }
128 if !valid.Alpha("abCD", "alpha").Ok {
129 t.Error("\"abCD\" are valid alpha characters should be true")
130 }
131 }
132
133 func TestNumeric(t *testing.T) {
134 valid := Validation{}
135
136 if valid.Numeric("a,1-@ $", "numeric").Ok {
137 t.Error("\"a,1-@ $\" are valid numeric characters should be false")
138 }
139 if !valid.Numeric("1234", "numeric").Ok {
140 t.Error("\"1234\" are valid numeric characters should be true")
141 }
142 }
143
144 func TestAlphaNumeric(t *testing.T) {
145 valid := Validation{}
146
147 if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok {
148 t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false")
149 }
150 if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok {
151 t.Error("\"1234aB\" are valid alpha or numeric characters should be true")
152 }
153 }
154
155 func TestMatch(t *testing.T) {
156 valid := Validation{}
157
158 if valid.Match("suchuangji@gmail", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok {
159 t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false")
160 }
161 if !valid.Match("suchuangji@gmail.com", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok {
162 t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true")
163 }
164 }
165
166 func TestNoMatch(t *testing.T) {
167 valid := Validation{}
168
169 if valid.NoMatch("123@gmail", regexp.MustCompile("[^\\w\\d]"), "nomatch").Ok {
170 t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false")
171 }
172 if !valid.NoMatch("123gmail", regexp.MustCompile("[^\\w\\d]"), "match").Ok {
173 t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true")
174 }
175 }
176
177 func TestAlphaDash(t *testing.T) {
178 valid := Validation{}
179
180 if valid.AlphaDash("a,1-@ $", "alphaDash").Ok {
181 t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false")
182 }
183 if !valid.AlphaDash("1234aB-_", "alphaDash").Ok {
184 t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true")
185 }
186 }
187
188 func TestEmail(t *testing.T) {
189 valid := Validation{}
190
191 if valid.Email("not@a email", "email").Ok {
192 t.Error("\"not@a email\" is a valid email address should be false")
193 }
194 if !valid.Email("suchuangji@gmail.com", "email").Ok {
195 t.Error("\"suchuangji@gmail.com\" is a valid email address should be true")
196 }
197 }
198
199 func TestIP(t *testing.T) {
200 valid := Validation{}
201
202 if valid.IP("11.255.255.256", "IP").Ok {
203 t.Error("\"11.255.255.256\" is a valid ip address should be false")
204 }
205 if !valid.IP("01.11.11.11", "IP").Ok {
206 t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true")
207 }
208 }
209
210 func TestBase64(t *testing.T) {
211 valid := Validation{}
212
213 if valid.Base64("suchuangji@gmail.com", "base64").Ok {
214 t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false")
215 }
216 if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok {
217 t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true")
218 }
219 }
1 package validation
2
3 import (
4 "fmt"
5 "reflect"
6 "regexp"
7 "time"
8 )
9
10 type Validator interface {
11 IsSatisfied(interface{}) bool
12 DefaultMessage() string
13 GetKey() string
14 }
15
16 type Required struct {
17 Key string
18 }
19
20 func (r Required) IsSatisfied(obj interface{}) bool {
21 if obj == nil {
22 return false
23 }
24
25 if str, ok := obj.(string); ok {
26 return len(str) > 0
27 }
28 if b, ok := obj.(bool); ok {
29 return b
30 }
31 if i, ok := obj.(int); ok {
32 return i != 0
33 }
34 if t, ok := obj.(time.Time); ok {
35 return !t.IsZero()
36 }
37 v := reflect.ValueOf(obj)
38 if v.Kind() == reflect.Slice {
39 return v.Len() > 0
40 }
41 return true
42 }
43
44 func (r Required) DefaultMessage() string {
45 return "Required"
46 }
47
48 func (r Required) GetKey() string {
49 return r.Key
50 }
51
52 type Min struct {
53 Min int
54 Key string
55 }
56
57 func (m Min) IsSatisfied(obj interface{}) bool {
58 num, ok := obj.(int)
59 if ok {
60 return num >= m.Min
61 }
62 return false
63 }
64
65 func (m Min) DefaultMessage() string {
66 return fmt.Sprintln("Minimum is", m.Min)
67 }
68
69 func (m Min) GetKey() string {
70 return m.Key
71 }
72
73 type Max struct {
74 Max int
75 Key string
76 }
77
78 func (m Max) IsSatisfied(obj interface{}) bool {
79 num, ok := obj.(int)
80 if ok {
81 return num <= m.Max
82 }
83 return false
84 }
85
86 func (m Max) DefaultMessage() string {
87 return fmt.Sprintln("Maximum is", m.Max)
88 }
89
90 func (m Max) GetKey() string {
91 return m.Key
92 }
93
94 // Requires an integer to be within Min, Max inclusive.
95 type Range struct {
96 Min
97 Max
98 Key string
99 }
100
101 func (r Range) IsSatisfied(obj interface{}) bool {
102 return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj)
103 }
104
105 func (r Range) DefaultMessage() string {
106 return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max)
107 }
108
109 func (r Range) GetKey() string {
110 return r.Key
111 }
112
113 // Requires an array or string to be at least a given length.
114 type MinSize struct {
115 Min int
116 Key string
117 }
118
119 func (m MinSize) IsSatisfied(obj interface{}) bool {
120 if str, ok := obj.(string); ok {
121 return len(str) >= m.Min
122 }
123 v := reflect.ValueOf(obj)
124 if v.Kind() == reflect.Slice {
125 return v.Len() >= m.Min
126 }
127 return false
128 }
129
130 func (m MinSize) DefaultMessage() string {
131 return fmt.Sprintln("Minimum size is", m.Min)
132 }
133
134 func (m MinSize) GetKey() string {
135 return m.Key
136 }
137
138 // Requires an array or string to be at most a given length.
139 type MaxSize struct {
140 Max int
141 Key string
142 }
143
144 func (m MaxSize) IsSatisfied(obj interface{}) bool {
145 if str, ok := obj.(string); ok {
146 return len(str) <= m.Max
147 }
148 v := reflect.ValueOf(obj)
149 if v.Kind() == reflect.Slice {
150 return v.Len() <= m.Max
151 }
152 return false
153 }
154
155 func (m MaxSize) DefaultMessage() string {
156 return fmt.Sprintln("Maximum size is", m.Max)
157 }
158
159 func (m MaxSize) GetKey() string {
160 return m.Key
161 }
162
163 // Requires an array or string to be exactly a given length.
164 type Length struct {
165 N int
166 Key string
167 }
168
169 func (l Length) IsSatisfied(obj interface{}) bool {
170 if str, ok := obj.(string); ok {
171 return len(str) == l.N
172 }
173 v := reflect.ValueOf(obj)
174 if v.Kind() == reflect.Slice {
175 return v.Len() == l.N
176 }
177 return false
178 }
179
180 func (l Length) DefaultMessage() string {
181 return fmt.Sprintln("Required length is", l.N)
182 }
183
184 func (l Length) GetKey() string {
185 return l.Key
186 }
187
188 type Alpha struct {
189 Key string
190 }
191
192 func (a Alpha) IsSatisfied(obj interface{}) bool {
193 if str, ok := obj.(string); ok {
194 for _, v := range str {
195 if ('Z' < v || v < 'A') && ('z' < v || v < 'a') {
196 return false
197 }
198 }
199 return true
200 }
201 return false
202 }
203
204 func (a Alpha) DefaultMessage() string {
205 return fmt.Sprintln("Must be valid alpha characters")
206 }
207
208 func (a Alpha) GetKey() string {
209 return a.Key
210 }
211
212 type Numeric struct {
213 Key string
214 }
215
216 func (n Numeric) IsSatisfied(obj interface{}) bool {
217 if str, ok := obj.(string); ok {
218 for _, v := range str {
219 if '9' < v || v < '0' {
220 return false
221 }
222 }
223 return true
224 }
225 return false
226 }
227
228 func (n Numeric) DefaultMessage() string {
229 return fmt.Sprintln("Must be valid numeric characters")
230 }
231
232 func (n Numeric) GetKey() string {
233 return n.Key
234 }
235
236 type AlphaNumeric struct {
237 Key string
238 }
239
240 func (a AlphaNumeric) IsSatisfied(obj interface{}) bool {
241 if str, ok := obj.(string); ok {
242 for _, v := range str {
243 if ('Z' < v || v < 'A') && ('z' < v || v < 'a') && ('9' < v || v < '0') {
244 return false
245 }
246 }
247 return true
248 }
249 return false
250 }
251
252 func (a AlphaNumeric) DefaultMessage() string {
253 return fmt.Sprintln("Must be valid alpha or numeric characters")
254 }
255
256 func (a AlphaNumeric) GetKey() string {
257 return a.Key
258 }
259
260 // Requires a string to match a given regex.
261 type Match struct {
262 Regexp *regexp.Regexp
263 Key string
264 }
265
266 func (m Match) IsSatisfied(obj interface{}) bool {
267 str := obj.(string)
268 return m.Regexp.MatchString(str)
269 }
270
271 func (m Match) DefaultMessage() string {
272 return fmt.Sprintln("Must match", m.Regexp)
273 }
274
275 func (m Match) GetKey() string {
276 return m.Key
277 }
278
279 // Requires a string to not match a given regex.
280 type NoMatch struct {
281 Match
282 Key string
283 }
284
285 func (n NoMatch) IsSatisfied(obj interface{}) bool {
286 return !n.Match.IsSatisfied(obj)
287 }
288
289 func (n NoMatch) DefaultMessage() string {
290 return fmt.Sprintln("Must not match", n.Regexp)
291 }
292
293 func (n NoMatch) GetKey() string {
294 return n.Key
295 }
296
297 var alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
298
299 type AlphaDash struct {
300 NoMatch
301 Key string
302 }
303
304 func (a AlphaDash) DefaultMessage() string {
305 return fmt.Sprintln("Must be valid alpha or numeric or dash(-_) characters")
306 }
307
308 func (a AlphaDash) GetKey() string {
309 return a.Key
310 }
311
312 var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
313
314 type Email struct {
315 Match
316 Key string
317 }
318
319 func (e Email) DefaultMessage() string {
320 return fmt.Sprintln("Must be a valid email address")
321 }
322
323 func (e Email) GetKey() string {
324 return e.Key
325 }
326
327 var ipPattern = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
328
329 type IP struct {
330 Match
331 Key string
332 }
333
334 func (i IP) DefaultMessage() string {
335 return fmt.Sprintln("Must be a valid ip address")
336 }
337
338 func (i IP) GetKey() string {
339 return i.Key
340 }
341
342 var base64Pattern = regexp.MustCompile("^(?:[A-Za-z0-99+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")
343
344 type Base64 struct {
345 Match
346 Key string
347 }
348
349 func (b Base64) DefaultMessage() string {
350 return fmt.Sprintln("Must be valid base64 characters")
351 }
352
353 func (b Base64) GetKey() string {
354 return b.Key
355 }
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!