diff --git a/validation/validation.go b/validation/validation.go new file mode 100644 index 00000000..a0544b6b --- /dev/null +++ b/validation/validation.go @@ -0,0 +1,177 @@ +package validation + +import ( + "fmt" + "regexp" +) + +type ValidationError struct { + Message, Key string +} + +// Returns the Message. +func (e *ValidationError) String() string { + if e == nil { + return "" + } + return e.Message +} + +// A Validation context manages data validation and error messages. +type Validation struct { + Errors []*ValidationError +} + +func (v *Validation) Clear() { + v.Errors = []*ValidationError{} +} + +func (v *Validation) HasErrors() bool { + return len(v.Errors) > 0 +} + +// Return the errors mapped by key. +// If there are multiple validation errors associated with a single key, the +// first one "wins". (Typically the first validation will be the more basic). +func (v *Validation) ErrorMap() map[string]*ValidationError { + m := map[string]*ValidationError{} + for _, e := range v.Errors { + if _, ok := m[e.Key]; !ok { + m[e.Key] = e + } + } + return m +} + +// Add an error to the validation context. +func (v *Validation) Error(message string, args ...interface{}) *ValidationResult { + result := (&ValidationResult{ + Ok: false, + Error: &ValidationError{}, + }).Message(message, args...) + v.Errors = append(v.Errors, result.Error) + return result +} + +// A ValidationResult is returned from every validation method. +// It provides an indication of success, and a pointer to the Error (if any). +type ValidationResult struct { + Error *ValidationError + Ok bool +} + +func (r *ValidationResult) Key(key string) *ValidationResult { + if r.Error != nil { + r.Error.Key = key + } + return r +} + +func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult { + if r.Error != nil { + if len(args) == 0 { + r.Error.Message = message + } else { + r.Error.Message = fmt.Sprintf(message, args) + } + } + return r +} + +// Test that the argument is non-nil and non-empty (if string or list) +func (v *Validation) Required(obj interface{}, key string) *ValidationResult { + return v.apply(Required{key}, obj) +} + +func (v *Validation) Min(n int, min int, key string) *ValidationResult { + return v.apply(Min{min, key}, n) +} + +func (v *Validation) Max(n int, max int, key string) *ValidationResult { + return v.apply(Max{max, key}, n) +} + +func (v *Validation) Range(n, min, max int, key string) *ValidationResult { + return v.apply(Range{Min{Min: min}, Max{Max: max}, key}, n) +} + +func (v *Validation) MinSize(obj interface{}, min int, key string) *ValidationResult { + return v.apply(MinSize{min, key}, obj) +} + +func (v *Validation) MaxSize(obj interface{}, max int, key string) *ValidationResult { + return v.apply(MaxSize{max, key}, obj) +} + +func (v *Validation) Length(obj interface{}, n int, key string) *ValidationResult { + return v.apply(Length{n, key}, obj) +} + +func (v *Validation) Alpha(obj interface{}, n int, key string) *ValidationResult { + return v.apply(Alpha{key}, obj) +} + +func (v *Validation) Numeric(obj interface{}, n int, key string) *ValidationResult { + return v.apply(Numeric{key}, obj) +} + +func (v *Validation) AlphaNumeric(obj interface{}, n int, key string) *ValidationResult { + return v.apply(AlphaNumeric{key}, obj) +} + +func (v *Validation) Match(str string, regex *regexp.Regexp, key string) *ValidationResult { + return v.apply(Match{regex, key}, str) +} + +func (v *Validation) NoMatch(str string, regex *regexp.Regexp, key string) *ValidationResult { + return v.apply(NoMatch{Match{Regexp: regex}, key}, str) +} + +func (v *Validation) AlphaDash(str string, key string) *ValidationResult { + return v.apply(AlphaDash{NoMatch{Match: Match{Regexp: alphaDashPattern}}, key}, str) +} + +func (v *Validation) Email(str string, key string) *ValidationResult { + return v.apply(Email{Match{Regexp: emailPattern}, key}, str) +} + +func (v *Validation) IP(str string, key string) *ValidationResult { + return v.apply(IP{Match{Regexp: ipPattern}, key}, str) +} + +func (v *Validation) Base64(str string, key string) *ValidationResult { + return v.apply(Base64{Match{Regexp: base64Pattern}, key}, str) +} + +func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult { + if chk.IsSatisfied(obj) { + return &ValidationResult{Ok: true} + } + + // Add the error to the validation context. + err := &ValidationError{ + Message: chk.DefaultMessage(), + Key: chk.GetKey(), + } + v.Errors = append(v.Errors, err) + + // Also return it in the result. + return &ValidationResult{ + Ok: false, + Error: err, + } +} + +// Apply a group of validators to a field, in order, and return the +// ValidationResult from the first one that fails, or the last one that +// succeeds. +func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult { + var result *ValidationResult + for _, check := range checks { + result = v.apply(check, obj) + if !result.Ok { + return result + } + } + return result +} diff --git a/validation/validators.go b/validation/validators.go index b0ab7072..7c359688 100644 --- a/validation/validators.go +++ b/validation/validators.go @@ -1,4 +1,4 @@ -package revel +package validation import ( "fmt" @@ -10,9 +10,12 @@ import ( type Validator interface { IsSatisfied(interface{}) bool DefaultMessage() string + GetKey() string } -type Required struct{} +type Required struct { + Key string +} func (r Required) IsSatisfied(obj interface{}) bool { if obj == nil { @@ -42,11 +45,13 @@ func (r Required) DefaultMessage() string { return "Required" } -// TODO -// type Unique struct {} +func (r Required) GetKey() string { + return r.Key +} type Min struct { Min int + Key string } func (m Min) IsSatisfied(obj interface{}) bool { @@ -61,8 +66,13 @@ func (m Min) DefaultMessage() string { return fmt.Sprintln("Minimum is", m.Min) } +func (m Min) GetKey() string { + return m.Key +} + type Max struct { Max int + Key string } func (m Max) IsSatisfied(obj interface{}) bool { @@ -77,10 +87,15 @@ func (m Max) DefaultMessage() string { return fmt.Sprintln("Maximum is", m.Max) } +func (m Max) GetKey() string { + return m.Key +} + // Requires an integer to be within Min, Max inclusive. type Range struct { Min Max + Key string } func (r Range) IsSatisfied(obj interface{}) bool { @@ -91,9 +106,14 @@ func (r Range) DefaultMessage() string { return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max) } +func (r Range) GetKey() string { + return r.Key +} + // Requires an array or string to be at least a given length. type MinSize struct { Min int + Key string } func (m MinSize) IsSatisfied(obj interface{}) bool { @@ -111,9 +131,14 @@ func (m MinSize) DefaultMessage() string { return fmt.Sprintln("Minimum size is", m.Min) } +func (m MinSize) GetKey() string { + return m.Key +} + // Requires an array or string to be at most a given length. type MaxSize struct { Max int + Key string } func (m MaxSize) IsSatisfied(obj interface{}) bool { @@ -131,27 +156,38 @@ func (m MaxSize) DefaultMessage() string { return fmt.Sprintln("Maximum size is", m.Max) } -// Requires an array or string to be exactly a given length. -type Length struct { - N int +func (m MaxSize) GetKey() string { + return m.Key } -func (s Length) IsSatisfied(obj interface{}) bool { +// Requires an array or string to be exactly a given length. +type Length struct { + N int + Key string +} + +func (l Length) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { - return len(str) == s.N + return len(str) == l.N } v := reflect.ValueOf(obj) if v.Kind() == reflect.Slice { - return v.Len() == s.N + return v.Len() == l.N } return false } -func (s Length) DefaultMessage() string { - return fmt.Sprintln("Required length is", s.N) +func (l Length) DefaultMessage() string { + return fmt.Sprintln("Required length is", l.N) } -type Alpha struct{} +func (l Length) GetKey() string { + return l.Key +} + +type Alpha struct { + Key string +} func (a Alpha) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { @@ -168,7 +204,13 @@ func (a Alpha) DefaultMessage() string { return fmt.Sprintln("Must be valid alpha characters") } -type Numeric struct{} +func (a Alpha) GetKey() string { + return a.Key +} + +type Numeric struct { + Key string +} func (n Numeric) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { @@ -185,7 +227,13 @@ func (n Numeric) DefaultMessage() string { return fmt.Sprintln("Must be valid numeric characters") } -type AlphaNumeric struct{} +func (n Numeric) GetKey() string { + return n.Key +} + +type AlphaNumeric struct { + Key string +} func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { @@ -202,9 +250,14 @@ func (a AlphaNumeric) DefaultMessage() string { return fmt.Sprintln("Must be valid alpha or numeric characters") } +func (a AlphaNumeric) GetKey() string { + return a.Key +} + // Requires a string to match a given regex. type Match struct { Regexp *regexp.Regexp + Key string } func (m Match) IsSatisfied(obj interface{}) bool { @@ -216,55 +269,84 @@ func (m Match) DefaultMessage() string { return fmt.Sprintln("Must match", m.Regexp) } +func (m Match) GetKey() string { + return m.Key +} + // Requires a string to not match a given regex. type NoMatch struct { Match + Key string } -func (m NoMatch) IsSatisfied(obj interface{}) bool { - return !m.Match.IsSatisfied(obj) +func (n NoMatch) IsSatisfied(obj interface{}) bool { + return !n.Match.IsSatisfied(obj) } -func (m NoMatch) DefaultMessage() string { - return fmt.Sprintln("Must no match", m.Regexp) +func (n NoMatch) DefaultMessage() string { + return fmt.Sprintln("Must no match", n.Regexp) +} + +func (n NoMatch) GetKey() string { + return n.Key } var alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") type AlphaDash struct { NoMatch + Key string } func (a AlphaDash) DefaultMessage() string { return fmt.Sprintln("Must be valid characters") } +func (a AlphaDash) GetKey() string { + return a.Key +} + var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") type Email struct { Match + Key string } func (e Email) DefaultMessage() string { return fmt.Sprintln("Must be a valid email address") } +func (e Email) GetKey() string { + return e.Key +} + 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?)") type IP struct { Match + Key string } func (i IP) DefaultMessage() string { return fmt.Sprintln("Must be a valid ip address") } +func (i IP) GetKey() string { + return i.Key +} + var base64Pattern = regexp.MustCompile("^(?:[A-Za-z0-99+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") type Base64 struct { Match + Key string } func (b Base64) DefaultMessage() string { return fmt.Sprintln("Must be valid base64 characters") } + +func (b Base64) GetKey() string { + return b.Key +}