forked from ebhomengo/niki
134 lines
3.3 KiB
Go
134 lines
3.3 KiB
Go
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
||
|
// Use of this source code is governed by a MIT-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
// Package validation provides configurable and extensible rules for validating data of various types.
|
||
|
package validation
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
// Validatable is the interface indicating the type implementing it supports data validation.
|
||
|
Validatable interface {
|
||
|
// Validate validates the data and returns an error if validation fails.
|
||
|
Validate() error
|
||
|
}
|
||
|
|
||
|
// Rule represents a validation rule.
|
||
|
Rule interface {
|
||
|
// Validate validates a value and returns a value if validation fails.
|
||
|
Validate(value interface{}) error
|
||
|
}
|
||
|
|
||
|
// RuleFunc represents a validator function.
|
||
|
// You may wrap it as a Rule by calling By().
|
||
|
RuleFunc func(value interface{}) error
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrorTag is the struct tag name used to customize the error field name for a struct field.
|
||
|
ErrorTag = "json"
|
||
|
|
||
|
// Skip is a special validation rule that indicates all rules following it should be skipped.
|
||
|
Skip = &skipRule{}
|
||
|
|
||
|
validatableType = reflect.TypeOf((*Validatable)(nil)).Elem()
|
||
|
)
|
||
|
|
||
|
// Validate validates the given value and returns the validation error, if any.
|
||
|
//
|
||
|
// Validate performs validation using the following steps:
|
||
|
// - validate the value against the rules passed in as parameters
|
||
|
// - if the value is a map and the map values implement `Validatable`, call `Validate` of every map value
|
||
|
// - if the value is a slice or array whose values implement `Validatable`, call `Validate` of every element
|
||
|
func Validate(value interface{}, rules ...Rule) error {
|
||
|
for _, rule := range rules {
|
||
|
if _, ok := rule.(*skipRule); ok {
|
||
|
return nil
|
||
|
}
|
||
|
if err := rule.Validate(value); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rv := reflect.ValueOf(value)
|
||
|
if (rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface) && rv.IsNil() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if v, ok := value.(Validatable); ok {
|
||
|
return v.Validate()
|
||
|
}
|
||
|
|
||
|
switch rv.Kind() {
|
||
|
case reflect.Map:
|
||
|
if rv.Type().Elem().Implements(validatableType) {
|
||
|
return validateMap(rv)
|
||
|
}
|
||
|
case reflect.Slice, reflect.Array:
|
||
|
if rv.Type().Elem().Implements(validatableType) {
|
||
|
return validateSlice(rv)
|
||
|
}
|
||
|
case reflect.Ptr, reflect.Interface:
|
||
|
return Validate(rv.Elem().Interface())
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// validateMap validates a map of validatable elements
|
||
|
func validateMap(rv reflect.Value) error {
|
||
|
errs := Errors{}
|
||
|
for _, key := range rv.MapKeys() {
|
||
|
if mv := rv.MapIndex(key).Interface(); mv != nil {
|
||
|
if err := mv.(Validatable).Validate(); err != nil {
|
||
|
errs[fmt.Sprintf("%v", key.Interface())] = err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if len(errs) > 0 {
|
||
|
return errs
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// validateMap validates a slice/array of validatable elements
|
||
|
func validateSlice(rv reflect.Value) error {
|
||
|
errs := Errors{}
|
||
|
l := rv.Len()
|
||
|
for i := 0; i < l; i++ {
|
||
|
if ev := rv.Index(i).Interface(); ev != nil {
|
||
|
if err := ev.(Validatable).Validate(); err != nil {
|
||
|
errs[strconv.Itoa(i)] = err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if len(errs) > 0 {
|
||
|
return errs
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type skipRule struct{}
|
||
|
|
||
|
func (r *skipRule) Validate(interface{}) error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type inlineRule struct {
|
||
|
f RuleFunc
|
||
|
}
|
||
|
|
||
|
func (r *inlineRule) Validate(value interface{}) error {
|
||
|
return r.f(value)
|
||
|
}
|
||
|
|
||
|
// By wraps a RuleFunc into a Rule.
|
||
|
func By(f RuleFunc) Rule {
|
||
|
return &inlineRule{f}
|
||
|
}
|