Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / validation.go
diff --git a/src/foundation/api/revel/validation.go b/src/foundation/api/revel/validation.go
new file mode 100644 (file)
index 0000000..49cf3a9
--- /dev/null
@@ -0,0 +1,334 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "fmt"
+       "net/http"
+       "net/url"
+       "regexp"
+       "runtime"
+)
+
+// ValidationError simple struct to store the Message & Key of a validation error
+type ValidationError struct {
+       Message, Key string
+}
+
+// String returns the Message field of the ValidationError struct.
+func (e *ValidationError) String() string {
+       if e == nil {
+               return ""
+       }
+       return e.Message
+}
+
+// Validation context manages data validation and error messages.
+type Validation struct {
+       Errors     []*ValidationError
+       Request    *Request
+       Translator func(locale, message string, args ...interface{}) string
+       keep       bool
+}
+
+// Keep tells revel to set a flash cookie on the client to make the validation
+// errors available for the next request.
+// This is helpful  when redirecting the client after the validation failed.
+// It is good practice to always redirect upon a HTTP POST request. Thus
+// one should use this method when HTTP POST validation failed and redirect
+// the user back to the form.
+func (v *Validation) Keep() {
+       v.keep = true
+}
+
+// Clear *all* ValidationErrors
+func (v *Validation) Clear() {
+       v.Errors = []*ValidationError{}
+}
+
+// HasErrors returns true if there are any (ie > 0) errors. False otherwise.
+func (v *Validation) HasErrors() bool {
+       return len(v.Errors) > 0
+}
+
+// ErrorMap returns 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
+}
+
+// Error adds an error to the validation context.
+func (v *Validation) Error(message string, args ...interface{}) *ValidationResult {
+       result := v.ValidationResult(false).Message(message, args...)
+       v.Errors = append(v.Errors, result.Error)
+       return result
+}
+
+// Error adds an error to the validation context.
+func (v *Validation) ErrorKey(message string, args ...interface{}) *ValidationResult {
+       result := v.ValidationResult(false).MessageKey(message, args...)
+       v.Errors = append(v.Errors, result.Error)
+       return result
+}
+
+// Error adds an error to the validation context.
+func (v *Validation) ValidationResult(ok bool) *ValidationResult {
+       if ok {
+               return &ValidationResult{Ok: ok}
+       } else {
+               return &ValidationResult{Ok: ok, Error: &ValidationError{}, Locale: v.Request.Locale, Translator: v.Translator}
+       }
+}
+
+// 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
+       Locale     string
+       Translator func(locale, message string, args ...interface{}) string
+}
+
+// Key sets the ValidationResult's Error "key" and returns itself for chaining
+func (r *ValidationResult) Key(key string) *ValidationResult {
+       if r.Error != nil {
+               r.Error.Key = key
+       }
+       return r
+}
+
+// Message sets the error message for a ValidationResult. Returns itself to
+// allow chaining.  Allows Sprintf() type calling with multiple parameters
+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
+}
+
+// Allow a message key to be passed into the validation result. The Validation has already
+// setup the translator to translate the message key
+func (r *ValidationResult) MessageKey(message string, args ...interface{}) *ValidationResult {
+       if r.Error == nil {
+               return r
+       }
+
+       // If translator found, use that to create the message, otherwise call Message method
+       if r.Translator != nil {
+               r.Error.Message = r.Translator(r.Locale, message, args...)
+       } else {
+               r.Message(message, args...)
+       }
+
+       return r
+}
+
+// Required tests that the argument is non-nil and non-empty (if string or list)
+func (v *Validation) Required(obj interface{}) *ValidationResult {
+       return v.apply(Required{}, obj)
+}
+
+func (v *Validation) Min(n int, min int) *ValidationResult {
+       return v.MinFloat(float64(n), float64(min))
+}
+
+func (v *Validation) MinFloat(n float64, min float64) *ValidationResult {
+       return v.apply(Min{min}, n)
+}
+
+func (v *Validation) Max(n int, max int) *ValidationResult {
+       return v.MaxFloat(float64(n), float64(max))
+}
+
+func (v *Validation) MaxFloat(n float64, max float64) *ValidationResult {
+       return v.apply(Max{max}, n)
+}
+
+func (v *Validation) Range(n, min, max int) *ValidationResult {
+       return v.RangeFloat(float64(n), float64(min), float64(max))
+}
+
+func (v *Validation) RangeFloat(n, min, max float64) *ValidationResult {
+       return v.apply(Range{Min{min}, Max{max}}, n)
+}
+
+func (v *Validation) MinSize(obj interface{}, min int) *ValidationResult {
+       return v.apply(MinSize{min}, obj)
+}
+
+func (v *Validation) MaxSize(obj interface{}, max int) *ValidationResult {
+       return v.apply(MaxSize{max}, obj)
+}
+
+func (v *Validation) Length(obj interface{}, n int) *ValidationResult {
+       return v.apply(Length{n}, obj)
+}
+
+func (v *Validation) Match(str string, regex *regexp.Regexp) *ValidationResult {
+       return v.apply(Match{regex}, str)
+}
+
+func (v *Validation) Email(str string) *ValidationResult {
+       return v.apply(Email{Match{emailPattern}}, str)
+}
+
+func (v *Validation) IPAddr(str string, cktype ...int) *ValidationResult {
+       return v.apply(IPAddr{cktype}, str)
+}
+
+func (v *Validation) MacAddr(str string) *ValidationResult {
+       return v.apply(IPAddr{}, str)
+}
+
+func (v *Validation) Domain(str string) *ValidationResult {
+       return v.apply(Domain{}, str)
+}
+
+func (v *Validation) URL(str string) *ValidationResult {
+       return v.apply(URL{}, str)
+}
+
+func (v *Validation) PureText(str string, m int) *ValidationResult {
+       return v.apply(PureText{m}, str)
+}
+
+func (v *Validation) FilePath(str string, m int) *ValidationResult {
+       return v.apply(FilePath{m}, str)
+}
+
+func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult {
+       if chk.IsSatisfied(obj) {
+               return v.ValidationResult(true)
+       }
+
+       // Get the default key.
+       var key string
+       if pc, _, line, ok := runtime.Caller(2); ok {
+               f := runtime.FuncForPC(pc)
+               if defaultKeys, ok := DefaultValidationKeys[f.Name()]; ok {
+                       key = defaultKeys[line]
+               }
+       } else {
+               utilLog.Error("Validation: Failed to get Caller information to look up Validation key")
+       }
+
+       // Add the error to the validation context.
+       err := &ValidationError{
+               Message: chk.DefaultMessage(),
+               Key:     key,
+       }
+       v.Errors = append(v.Errors, err)
+
+       // Also return it in the result.
+       vr := v.ValidationResult(false)
+       vr.Error = err
+       return vr
+}
+
+// Check applies 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
+}
+
+// ValidationFilter revel Filter function to be hooked into the filter chain.
+func ValidationFilter(c *Controller, fc []Filter) {
+       // If json request, we shall assume json response is intended,
+       // as such no validation cookies should be tied response
+       if c.Params != nil && c.Params.JSON != nil {
+               c.Validation = &Validation{Request: c.Request, Translator: MessageFunc}
+               fc[0](c, fc[1:])
+       } else {
+               errors, err := restoreValidationErrors(c.Request)
+               c.Validation = &Validation{
+                       Errors:     errors,
+                       keep:       false,
+                       Request:    c.Request,
+                       Translator: MessageFunc,
+               }
+               hasCookie := (err != http.ErrNoCookie)
+
+               fc[0](c, fc[1:])
+
+               // Add Validation errors to ViewArgs.
+               c.ViewArgs["errors"] = c.Validation.ErrorMap()
+
+               // Store the Validation errors
+               var errorsValue string
+               if c.Validation.keep {
+                       for _, err := range c.Validation.Errors {
+                               if err.Message != "" {
+                                       errorsValue += "\x00" + err.Key + ":" + err.Message + "\x00"
+                               }
+                       }
+               }
+
+               // When there are errors from Validation and Keep() has been called, store the
+               // values in a cookie. If there previously was a cookie but no errors, remove
+               // the cookie.
+               if errorsValue != "" {
+                       c.SetCookie(&http.Cookie{
+                               Name:     CookiePrefix + "_ERRORS",
+                               Value:    url.QueryEscape(errorsValue),
+                               Domain:   CookieDomain,
+                               Path:     "/",
+                               HttpOnly: true,
+                               Secure:   CookieSecure,
+                       })
+               } else if hasCookie {
+                       c.SetCookie(&http.Cookie{
+                               Name:     CookiePrefix + "_ERRORS",
+                               MaxAge:   -1,
+                               Domain:   CookieDomain,
+                               Path:     "/",
+                               HttpOnly: true,
+                               Secure:   CookieSecure,
+                       })
+               }
+       }
+}
+
+// Restore Validation.Errors from a request.
+func restoreValidationErrors(req *Request) ([]*ValidationError, error) {
+       var (
+               err    error
+               cookie ServerCookie
+               errors = make([]*ValidationError, 0, 5)
+       )
+       if cookie, err = req.Cookie(CookiePrefix + "_ERRORS"); err == nil {
+               ParseKeyValueCookie(cookie.GetValue(), func(key, val string) {
+                       errors = append(errors, &ValidationError{
+                               Key:     key,
+                               Message: val,
+                       })
+               })
+       }
+       return errors, err
+}
+
+// DefaultValidationKeys register default validation keys for all calls to Controller.Validation.Func().
+// Map from (package).func => (line => name of first arg to Validation func)
+// E.g. "myapp/controllers.helper" or "myapp/controllers.(*Application).Action"
+// This is set on initialization in the generated main.go file.
+var DefaultValidationKeys map[string]map[int]string