mirror of
https://gitee.com/johng/gf.git
synced 2024-12-12 12:45:08 +08:00
308 lines
17 KiB
Go
308 lines
17 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
// Package gvalid implements powerful and useful data/form validation functionality.
|
|
package gvalid
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/text/gregex"
|
|
)
|
|
|
|
// Refer to Laravel validation:
|
|
// https://laravel.com/docs/5.5/validation#available-validation-rules
|
|
// https://learnku.com/docs/laravel/5.4/validation
|
|
//
|
|
// All supported rules:
|
|
// required format: required brief: Required.
|
|
// required-if format: required-if:field,value,... brief: Required unless all given field and its value are equal.
|
|
// required-unless format: required-unless:field,value,... brief: Required unless all given field and its value are not equal.
|
|
// required-with format: required-with:field1,field2,... brief: Required if any of given fields are not empty.
|
|
// required-with-all format: required-with-all:field1,field2,... brief: Required if all given fields are not empty.
|
|
// required-without format: required-without:field1,field2,... brief: Required if any of given fields are empty.
|
|
// required-without-all format: required-without-all:field1,field2,...brief: Required if all given fields are empty.
|
|
// bail format: bail brief: Stop validating when this field's validation failed.
|
|
// date format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02
|
|
// date-format format: date-format:format brief: Custom date format.
|
|
// email format: email brief: Email address.
|
|
// phone format: phone brief: Phone number.
|
|
// telephone format: telephone brief: Telephone number, like: "XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
|
// passport format: passport brief: Universal passport format rule: Starting with letter, containing only numbers or underscores, length between 6 and 18
|
|
// password format: password brief: Universal password format rule1: Containing any visible chars, length between 6 and 18.
|
|
// password2 format: password2 brief: Universal password format rule2: Must meet password rule1, must contain lower and upper letters and numbers.
|
|
// password3 format: password3 brief: Universal password format rule3: Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
|
// postcode format: postcode brief: Postcode number.
|
|
// resident-id format: resident-id brief: Resident id number.
|
|
// bank-card format: bank-card brief: Bank card nunber.
|
|
// qq format: qq brief: Tencent QQ number.
|
|
// ip format: ip brief: IPv4/IPv6.
|
|
// ipv4 format: ipv4 brief: IPv4.
|
|
// ipv6 format: ipv6 brief: IPv6.
|
|
// mac format: mac brief: MAC.
|
|
// url format: url brief: URL.
|
|
// domain format: domain brief: Domain.
|
|
// length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
|
// min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
|
// max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
|
// size format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
|
// between format: between:min,max brief: Range between :min and :max. It supports both integer and float.
|
|
// min format: min:min brief: Equal or greater than :min. It supports both integer and float.
|
|
// max format: max:max brief: Equal or lesser than :max. It supports both integer and float.
|
|
// json format: json brief: JSON.
|
|
// integer format: integer brief: Integer.
|
|
// float format: float brief: Float. Note that an integer is actually a float number.
|
|
// boolean format: boolean brief: Boolean(1,true,on,yes:true | 0,false,off,no,"":false)
|
|
// same format: same:field brief: Value should be the same as value of field.
|
|
// different format: different:field brief: Value should be different from value of field.
|
|
// in format: in:value1,value2,... brief: Value should be in: value1,value2,...
|
|
// not-in format: not-in:value1,value2,... brief: Value should not be in: value1,value2,...
|
|
// regex format: regex:pattern brief: Value should match custom regular expression pattern.
|
|
|
|
// CustomMsg is the custom error message type,
|
|
// like: map[field] => string|map[rule]string
|
|
type CustomMsg = map[string]interface{}
|
|
|
|
// fieldRule defined the alias name and rule string for specified field.
|
|
type fieldRule struct {
|
|
Name string // Alias name for the field.
|
|
Rule string // Rule string like: "max:6"
|
|
}
|
|
|
|
// apiNoValidation is an interface that marks current struct not validated by package `gvalid`.
|
|
type apiNoValidation interface {
|
|
NoValidation()
|
|
}
|
|
|
|
const (
|
|
singleRulePattern = `^([\w-]+):{0,1}(.*)` // regular expression pattern for single validation rule.
|
|
internalRulesErrRuleName = "InvalidRules" // rule name for internal invalid rules validation error.
|
|
internalParamsErrRuleName = "InvalidParams" // rule name for internal invalid params validation error.
|
|
internalObjectErrRuleName = "InvalidObject" // rule name for internal invalid object validation error.
|
|
internalErrorMapKey = "__InternalError__" // error map key for internal errors.
|
|
internalDefaultRuleName = "__default__" // default rule name for i18n error message format if no i18n message found for specified error rule.
|
|
ruleMessagePrefixForI18n = "gf.gvalid.rule." // prefix string for each rule configuration in i18n content.
|
|
noValidationTagName = "nv" // no validation tag name for struct attribute.
|
|
bailRuleName = "bail" // the name for rule "bail"
|
|
)
|
|
|
|
var (
|
|
defaultValidator = New() // defaultValidator is the default validator for package functions.
|
|
structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array.
|
|
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
|
|
|
|
// all internal error keys.
|
|
internalErrKeyMap = map[string]string{
|
|
internalRulesErrRuleName: internalRulesErrRuleName,
|
|
internalParamsErrRuleName: internalParamsErrRuleName,
|
|
internalObjectErrRuleName: internalObjectErrRuleName,
|
|
}
|
|
// regular expression object for single rule
|
|
// which is compiled just once and of repeatable usage.
|
|
ruleRegex, _ = regexp.Compile(singleRulePattern)
|
|
|
|
// mustCheckRulesEvenValueEmpty specifies some rules that must be validated
|
|
// even the value is empty (nil or empty).
|
|
mustCheckRulesEvenValueEmpty = map[string]struct{}{
|
|
"required": {},
|
|
"required-if": {},
|
|
"required-unless": {},
|
|
"required-with": {},
|
|
"required-with-all": {},
|
|
"required-without": {},
|
|
"required-without-all": {},
|
|
//"same": {},
|
|
//"different": {},
|
|
//"in": {},
|
|
//"not-in": {},
|
|
//"regex": {},
|
|
}
|
|
// allSupportedRules defines all supported rules that is used for quick checks.
|
|
allSupportedRules = map[string]struct{}{
|
|
"required": {},
|
|
"required-if": {},
|
|
"required-unless": {},
|
|
"required-with": {},
|
|
"required-with-all": {},
|
|
"required-without": {},
|
|
"required-without-all": {},
|
|
"bail": {},
|
|
"date": {},
|
|
"date-format": {},
|
|
"email": {},
|
|
"phone": {},
|
|
"phone-loose": {},
|
|
"telephone": {},
|
|
"passport": {},
|
|
"password": {},
|
|
"password2": {},
|
|
"password3": {},
|
|
"postcode": {},
|
|
"resident-id": {},
|
|
"bank-card": {},
|
|
"qq": {},
|
|
"ip": {},
|
|
"ipv4": {},
|
|
"ipv6": {},
|
|
"mac": {},
|
|
"url": {},
|
|
"domain": {},
|
|
"length": {},
|
|
"min-length": {},
|
|
"max-length": {},
|
|
"size": {},
|
|
"between": {},
|
|
"min": {},
|
|
"max": {},
|
|
"json": {},
|
|
"integer": {},
|
|
"float": {},
|
|
"boolean": {},
|
|
"same": {},
|
|
"different": {},
|
|
"in": {},
|
|
"not-in": {},
|
|
"regex": {},
|
|
}
|
|
// boolMap defines the boolean values.
|
|
boolMap = map[string]struct{}{
|
|
"1": {},
|
|
"true": {},
|
|
"on": {},
|
|
"yes": {},
|
|
"": {},
|
|
"0": {},
|
|
"false": {},
|
|
"off": {},
|
|
"no": {},
|
|
}
|
|
// defaultMessages is the default error messages.
|
|
// Note that these messages are synchronized from ./i18n/en/validation.toml .
|
|
defaultMessages = map[string]string{
|
|
"required": "The :attribute field is required",
|
|
"required-if": "The :attribute field is required",
|
|
"required-unless": "The :attribute field is required",
|
|
"required-with": "The :attribute field is required",
|
|
"required-with-all": "The :attribute field is required",
|
|
"required-without": "The :attribute field is required",
|
|
"required-without-all": "The :attribute field is required",
|
|
"date": "The :attribute value is not a valid date",
|
|
"date-format": "The :attribute value does not match the format :format",
|
|
"email": "The :attribute value must be a valid email address",
|
|
"phone": "The :attribute value must be a valid phone number",
|
|
"telephone": "The :attribute value must be a valid telephone number",
|
|
"passport": "The :attribute value is not a valid passport format",
|
|
"password": "The :attribute value is not a valid passport format",
|
|
"password2": "The :attribute value is not a valid passport format",
|
|
"password3": "The :attribute value is not a valid passport format",
|
|
"postcode": "The :attribute value is not a valid passport format",
|
|
"resident-id": "The :attribute value is not a valid resident id number",
|
|
"bank-card": "The :attribute value must be a valid bank card number",
|
|
"qq": "The :attribute value must be a valid QQ number",
|
|
"ip": "The :attribute value must be a valid IP address",
|
|
"ipv4": "The :attribute value must be a valid IPv4 address",
|
|
"ipv6": "The :attribute value must be a valid IPv6 address",
|
|
"mac": "The :attribute value must be a valid MAC address",
|
|
"url": "The :attribute value must be a valid URL address",
|
|
"domain": "The :attribute value must be a valid domain format",
|
|
"length": "The :attribute value length must be between :min and :max",
|
|
"min-length": "The :attribute value length must be equal or greater than :min",
|
|
"max-length": "The :attribute value length must be equal or lesser than :max",
|
|
"size": "The :attribute value length must be :size",
|
|
"between": "The :attribute value must be between :min and :max",
|
|
"min": "The :attribute value must be equal or greater than :min",
|
|
"max": "The :attribute value must be equal or lesser than :max",
|
|
"json": "The :attribute value must be a valid JSON string",
|
|
"xml": "The :attribute value must be a valid XML string",
|
|
"array": "The :attribute value must be an array",
|
|
"integer": "The :attribute value must be an integer",
|
|
"float": "The :attribute value must be a float",
|
|
"boolean": "The :attribute value field must be true or false",
|
|
"same": "The :attribute value must be the same as field :field",
|
|
"different": "The :attribute value must be different from field :field",
|
|
"in": "The :attribute value is not in acceptable range",
|
|
"not-in": "The :attribute value is not in acceptable range",
|
|
"regex": "The :attribute value is invalid",
|
|
internalDefaultRuleName: "The :attribute value is invalid",
|
|
}
|
|
// markedRuleMap defines all rules that are just marked rules which have neither functional meaning
|
|
// nor error messages.
|
|
markedRuleMap = map[string]bool{
|
|
bailRuleName: true,
|
|
//"nullable": true,
|
|
}
|
|
)
|
|
|
|
// CheckValue checks single value with specified rules.
|
|
// It returns nil if successful validation.
|
|
//
|
|
// The parameter `value` can be any type of variable, which will be converted to string
|
|
// for validation.
|
|
// The parameter `rules` can be one or more rules, multiple rules joined using char '|'.
|
|
// The parameter `messages` specifies the custom error messages, which can be type of:
|
|
// string/map/struct/*struct.
|
|
// The optional parameter `params` specifies the extra validation parameters for some rules
|
|
// like: required-*、same、different, etc.
|
|
func CheckValue(ctx context.Context, value interface{}, rules string, messages interface{}, params ...interface{}) Error {
|
|
var data interface{}
|
|
if len(params) > 0 {
|
|
data = params[0]
|
|
}
|
|
return defaultValidator.Ctx(ctx).Rules(rules).Data(data).Messages(messages).CheckValue(value)
|
|
}
|
|
|
|
// CheckMap validates map and returns the error result. It returns nil if with successful validation.
|
|
//
|
|
// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result
|
|
// if `rules` is type of []string.
|
|
// The optional parameter `messages` specifies the custom error messages for specified keys and rules.
|
|
func CheckMap(ctx context.Context, params interface{}, rules interface{}, messages ...CustomMsg) Error {
|
|
var customErrorMessages CustomMsg
|
|
if len(messages) > 0 {
|
|
customErrorMessages = messages[0]
|
|
}
|
|
return defaultValidator.Ctx(ctx).Rules(rules).Messages(customErrorMessages).CheckMap(params)
|
|
}
|
|
|
|
// CheckStruct validates struct and returns the error result.
|
|
//
|
|
// The parameter `object` should be type of struct/*struct.
|
|
// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result
|
|
// if `rules` is type of []string.
|
|
// The optional parameter `messages` specifies the custom error messages for specified keys and rules.
|
|
func CheckStruct(ctx context.Context, object interface{}, rules interface{}, messages ...CustomMsg) Error {
|
|
var customErrorMessages CustomMsg
|
|
if len(messages) > 0 {
|
|
customErrorMessages = messages[0]
|
|
}
|
|
return defaultValidator.Ctx(ctx).Rules(rules).Messages(customErrorMessages).CheckStruct(object)
|
|
}
|
|
|
|
// CheckStructWithData validates struct with given parameter map and returns the error result.
|
|
//
|
|
// The parameter `object` should be type of struct/*struct.
|
|
// The parameter `rules` can be type of []string/map[string]string. It supports sequence in error result
|
|
// if `rules` is type of []string.
|
|
// The optional parameter `messages` specifies the custom error messages for specified keys and rules.
|
|
func CheckStructWithData(ctx context.Context, object interface{}, data interface{}, rules interface{}, messages ...CustomMsg) Error {
|
|
var customErrorMessages CustomMsg
|
|
if len(messages) > 0 {
|
|
customErrorMessages = messages[0]
|
|
}
|
|
return defaultValidator.Ctx(ctx).Data(data).Rules(rules).Messages(customErrorMessages).CheckStruct(object)
|
|
}
|
|
|
|
// parseSequenceTag parses one sequence tag to field, rule and error message.
|
|
// The sequence tag is like: [alias@]rule[...#msg...]
|
|
func parseSequenceTag(tag string) (field, rule, msg string) {
|
|
// Complete sequence tag.
|
|
// Example: name@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致
|
|
match, _ := gregex.MatchString(`\s*((\w+)\s*@){0,1}\s*([^#]+)\s*(#\s*(.*)){0,1}\s*`, tag)
|
|
return strings.TrimSpace(match[2]), strings.TrimSpace(match[3]), strings.TrimSpace(match[5])
|
|
}
|