Validator

這裡介紹如何加入你客製化的變數和 Validation

加入你自己的客製化變數

關鍵是你要先了解反射 “reflect”

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-playground/validator/v10"
	"gopkg.in/guregu/null.v4"
)

/* 
 * 這邊我是使用 guregu/null 的 int
 * 請使用 func(field reflect.Value) interface{} 樣板來定義
*/
func nullIntValidator(field reflect.Value) interface{} {
	if valuer, ok := field.Interface().(null.Int); ok {
		if valuer.Valid {
			return valuer.Int64
		}
	}
	return nil
}

func main() {
	Validate = validator.New()
	// 使用 RegisterCustomTypeFunc 函式來加入
	Validate.RegisterCustomTypeFunc(nullIntValidator, null.Int{})
}

加入你自己的客製化 Validation

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-playground/validator/v10"
	"gopkg.in/guregu/null.v4"
)

/* 
 * 進來的時間要大於現在多少時間
 * 請使用 func(fl validator.FieldLevel) bool 樣板來定義
*/
func bookAbleDate(fl validator.FieldLevel) bool {
	field := fl.Field()
	params := parseOneOfParam2(fl.Param())
	if params == nil {
		if field.Kind() == reflect.Struct {
			fTime := field.Interface().(time.Time)
			return fTime.After(time.Now())
		}
	} else if len(params) == 2 {
		var afterTime time.Duration
		pTimeInt64, err := strconv.ParseInt(params[0], 10, 64)
		if err != nil {
			return false
		}
		pTime := time.Duration(pTimeInt64)
		switch params[1] {
		case "s":
			afterTime = pTime * time.Second
		case "m":
			afterTime = pTime * time.Minute
		case "h":
			afterTime = pTime * time.Hour
		default:
			return false
		}
		if field.Kind() == reflect.Struct {
			fTime := field.Interface().(time.Time)
			return fTime.After(time.Now().Add(afterTime))
		}
	} else {
		return false
	}

	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}

func main() {
	Validate = validator.New()
	// 使用 RegisterValidation 函式來加入
	// bookdatetime=10 m
	_ = Validate.RegisterValidation("bookabledate", bookAbleDate)
}

完整範例

package valider

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-playground/validator/v10"
	"gopkg.in/guregu/null.v4"
)

const (
	splitParamsRegexString = `'[^']*'|\S+`
)

// use a single instance of Validate, it caches struct info
var (
	Validate         *validator.Validate
	splitParamsRegex = regexp.MustCompile(splitParamsRegexString)
)

var oneofValsCache = map[string][]string{}
var oneofValsCacheRWLock = sync.RWMutex{}

func parseOneOfParam2(s string) []string {
	oneofValsCacheRWLock.RLock()
	vals, ok := oneofValsCache[s]
	oneofValsCacheRWLock.RUnlock()
	if !ok {
		oneofValsCacheRWLock.Lock()
		vals = splitParamsRegex.FindAllString(s, -1)
		for i := 0; i < len(vals); i++ {
			vals[i] = strings.Replace(vals[i], "'", "", -1)
		}
		oneofValsCache[s] = vals
		oneofValsCacheRWLock.Unlock()
	}
	return vals
}

func nullTimeValidator(field reflect.Value) interface{} {
	if valuer, ok := field.Interface().(null.Time); ok {
		if valuer.Valid {
			return valuer.Time
		}
	}
	return nil
}

func nullBoolValidator(field reflect.Value) interface{} {
	if valuer, ok := field.Interface().(null.Bool); ok {
		if valuer.Valid {
			return valuer.Bool
		}
	}
	return nil
}

func nullIntValidator(field reflect.Value) interface{} {
	if valuer, ok := field.Interface().(null.Int); ok {
		if valuer.Valid {
			return valuer.Int64
		}
	}
	return nil
}

func bookAbleDate(fl validator.FieldLevel) bool {
	field := fl.Field()
	params := parseOneOfParam2(fl.Param())
	if params == nil {
		if field.Kind() == reflect.Struct {
			fTime := field.Interface().(time.Time)
			return fTime.After(time.Now())
		}
	} else if len(params) == 2 {
		var afterTime time.Duration
		pTimeInt64, err := strconv.ParseInt(params[0], 10, 64)
		if err != nil {
			return false
		}
		pTime := time.Duration(pTimeInt64)
		switch params[1] {
		case "s":
			afterTime = pTime * time.Second
		case "m":
			afterTime = pTime * time.Minute
		case "h":
			afterTime = pTime * time.Hour
		default:
			return false
		}
		if field.Kind() == reflect.Struct {
			fTime := field.Interface().(time.Time)
			return fTime.After(time.Now().Add(afterTime))
		}
	} else {
		return false
	}

	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}

// requireCheckField is a func for check field kind
func requireCheckFieldKind(fl validator.FieldLevel, param string, defaultNotFoundValue bool) bool {
	field := fl.Field()
	kind := field.Kind()
	var nullable, found bool
	if len(param) > 0 {
		field, kind, nullable, found = fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
		if !found {
			return defaultNotFoundValue
		}
	}
	switch kind {
	case reflect.Invalid:
		return defaultNotFoundValue
	case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
		return field.IsNil()
	case reflect.Int64: // for null.Int
		return field.Int() < 0
	case reflect.Bool: // for null.Bool
		return !defaultNotFoundValue
	default:
		if nullable && field.Interface() != nil {
			return false
		}
		return field.IsValid() && field.Interface() == reflect.Zero(field.Type()).Interface()
	}
}

func requiredWithoutAllBool(fl validator.FieldLevel) bool {
	field := fl.Field()
	params := parseOneOfParam2(fl.Param())
	if params == nil {
		return false
	}
	if len(params) == 0 {
		return false
	}

	fbool, err := strconv.ParseBool(params[0])
	if err != nil {
		return false
	}

	allIsNull := true
	params = params[1:]
	for _, param := range params {
		if !requireCheckFieldKind(fl, param, true) {
			allIsNull = false
			break
		}
	}

	if field.Kind() == reflect.Bool {
		fBool := field.Bool()
		if allIsNull {
			return fBool == fbool
		}
		return fBool != fbool
	}

	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}

func Init() {
	Validate = validator.New()

	Validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]

		if name == "-" {
			return ""
		}

		return name
	})

	Validate.RegisterCustomTypeFunc(nullTimeValidator, null.Time{})

	Validate.RegisterCustomTypeFunc(nullBoolValidator, null.Bool{})

	Validate.RegisterCustomTypeFunc(nullIntValidator, null.Int{})

	// bookdatetime=10 m
	_ = Validate.RegisterValidation("bookabledate", bookAbleDate)

	// required_without_all_bool=true field1 field12
	_ = Validate.RegisterValidation("required_without_all_bool", requiredWithoutAllBool)
}