validation
API
validation
packageAPI reference for the validation
package.
Imports
(8)
T
type
Rule
Rule validates a field value given an optional parameter.
pkg/validation/validator.go:15-15
type Rule func(value reflect.Value, param string) error
S
struct
compiledField
pkg/validation/validator.go:17-20
type compiledField struct
Fields
| Name | Type | Description |
|---|---|---|
| Index | int | |
| Rules | []compiledRule |
S
struct
compiledRule
pkg/validation/validator.go:22-26
type compiledRule struct
Fields
| Name | Type | Description |
|---|---|---|
| Name | string | |
| Rule | Rule | |
| Param | string |
Uses
S
struct
typeCache
pkg/validation/validator.go:28-30
type typeCache struct
Fields
| Name | Type | Description |
|---|---|---|
| fields | []compiledField |
S
struct
Validator
Validator validates structs based on “validate” struct tags.
pkg/validation/validator.go:33-41
type Validator struct
Methods
Register
Method
Parameters
name
string
rule
Rule
func (*Validator) Register(name string, rule Rule)
{
v.mu.Lock()
defer v.mu.Unlock()
v.rules[name] = rule
}
Validate
Method
Parameters
target
any
Returns
func (*Validator) Validate(target any) Errors
{
val := reflect.ValueOf(target)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return Errors{{Field: "", Message: "target must be a struct"}}
}
typ := val.Type()
v.typeMu.RLock()
tc, ok := v.typeCache[typ]
v.typeMu.RUnlock()
if !ok {
v.typeMu.Lock()
tc, ok = v.typeCache[typ]
if !ok {
tc = v.compileType(typ, target)
v.typeCache[typ] = tc
}
v.typeMu.Unlock()
}
v.mu.RLock()
defer v.mu.RUnlock()
var errs Errors
for _, cf := range tc.fields {
fieldVal := val.Field(cf.Index)
for _, cr := range cf.Rules {
if err := cr.Rule(fieldVal, cr.Param); err != nil {
errs = append(errs, Error{
Field: typ.Field(cf.Index).Name,
Message: err.Error(),
})
}
}
}
return errs
}
compileType
Method
Parameters
typ
reflect.Type
target
any
Returns
func (*Validator) compileType(typ reflect.Type, target any) *typeCache
{
fields := tagParser.ParseStruct(target)
v.mu.RLock()
defer v.mu.RUnlock()
tc := &typeCache{}
for _, meta := range fields {
tag := meta.RawTag
if tag == "" {
continue
}
cf := compiledField{Index: meta.Index}
rules := strings.Split(tag, ",")
for _, ruleDef := range rules {
ruleDef = strings.TrimSpace(ruleDef)
if ruleDef == "" {
continue
}
var ruleName, param string
if idx := strings.Index(ruleDef, "="); idx != -1 {
ruleName = ruleDef[:idx]
param = ruleDef[idx+1:]
} else {
ruleName = ruleDef
}
if rule, ok := v.rules[ruleName]; ok {
cf.Rules = append(cf.Rules, compiledRule{Name: ruleName, Rule: rule, Param: param})
}
}
if len(cf.Rules) > 0 {
tc.fields = append(tc.fields, cf)
}
}
return tc
}
registerBuiltin
Method
func (*Validator) registerBuiltin()
{
v.rules["required"] = func(field reflect.Value, _ string) error {
switch field.Kind() {
case reflect.String:
if field.String() == "" {
return fmt.Errorf("required")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
case reflect.Float32, reflect.Float64:
case reflect.Slice, reflect.Map:
if field.Len() == 0 {
return fmt.Errorf("required")
}
case reflect.Bool:
default:
if field.IsZero() {
return fmt.Errorf("required")
}
}
return nil
}
v.rules["min"] = func(field reflect.Value, param string) error {
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
min, err := strconv.ParseInt(param, 10, 64)
if err != nil {
return nil
}
if field.Int() < min {
return fmt.Errorf("min %v", min)
}
case reflect.Float32, reflect.Float64:
min, err := strconv.ParseFloat(param, 64)
if err != nil {
return nil
}
if field.Float() < min {
return fmt.Errorf("min %v", min)
}
}
return nil
}
v.rules["max"] = func(field reflect.Value, param string) error {
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
max, err := strconv.ParseInt(param, 10, 64)
if err != nil {
return nil
}
if field.Int() > max {
return fmt.Errorf("max %v", max)
}
case reflect.Float32, reflect.Float64:
max, err := strconv.ParseFloat(param, 64)
if err != nil {
return nil
}
if field.Float() > max {
return fmt.Errorf("max %v", max)
}
}
return nil
}
v.rules["email"] = func(field reflect.Value, _ string) error {
if field.Kind() != reflect.String {
return nil
}
if !v.emailRegex.MatchString(field.String()) {
return fmt.Errorf("invalid email")
}
return nil
}
v.rules["pattern"] = func(field reflect.Value, param string) error {
if field.Kind() != reflect.String {
return nil
}
v.cacheMu.RLock()
re, ok := v.cache[param]
v.cacheMu.RUnlock()
if !ok {
v.cacheMu.Lock()
re, ok = v.cache[param]
if !ok {
var err error
re, err = regexp.Compile(param)
if err != nil {
v.cacheMu.Unlock()
return nil
}
v.cache[param] = re
}
v.cacheMu.Unlock()
}
if !re.MatchString(field.String()) {
return fmt.Errorf("pattern mismatch")
}
return nil
}
}
Fields
| Name | Type | Description |
|---|---|---|
| mu | sync.RWMutex | |
| rules | map[string]Rule | |
| emailRegex | *regexp.Regexp | |
| cache | map[string]*regexp.Regexp | |
| cacheMu | sync.RWMutex | |
| typeCache | map[reflect.Type]*typeCache | |
| typeMu | sync.RWMutex |
S
struct
Error
Error represents a single field validation error.
pkg/validation/validator.go:44-47
type Error struct
Methods
Error
Method
Returns
string
func (Error) Error() string
{
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
Fields
| Name | Type | Description |
|---|---|---|
| Field | string | json:"field" |
| Message | string | json:"message" |
T
type
Errors
Errors is a collection of validation errors.
pkg/validation/validator.go:54-54
type Errors []Error
F
function
New
New creates a new Validator with built-in rules.
Returns
pkg/validation/validator.go:65-74
func New() *Validator
{
v := &Validator{
rules: make(map[string]Rule),
emailRegex: regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`),
cache: make(map[string]*regexp.Regexp),
typeCache: make(map[reflect.Type]*typeCache),
}
v.registerBuiltin()
return v
}
S
struct
user
pkg/validation/validator_test.go:7-12
type user struct
Fields
| Name | Type | Description |
|---|---|---|
| Name | string | validate:"required" |
| Age | int | validate:"min=18,max=99" |
| string | validate:"email" | |
| Label | string | validate:"pattern=^[a-z]+$" |
S
struct
optionalInt
pkg/validation/validator_test.go:14-16
type optionalInt struct
Fields
| Name | Type | Description |
|---|---|---|
| Count | int | validate:"required" |
F
function
TestValidator_RequiredString
Parameters
t
pkg/validation/validator_test.go:18-34
func TestValidator_RequiredString(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "", Email: "[email protected]"})
if len(errs) == 0 {
t.Error("expected error for empty Name")
}
found := false
for _, e := range errs {
if e.Field == "Name" {
found = true
break
}
}
if !found {
t.Error("expected error on field Name")
}
}
F
function
TestValidator_MinInt
Parameters
t
pkg/validation/validator_test.go:36-49
func TestValidator_MinInt(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "x", Age: 10, Email: "[email protected]"})
found := false
for _, e := range errs {
if e.Field == "Age" {
found = true
break
}
}
if !found {
t.Error("expected error for Age < 18")
}
}
F
function
TestValidator_MaxInt
Parameters
t
pkg/validation/validator_test.go:51-64
func TestValidator_MaxInt(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "x", Age: 100, Email: "[email protected]"})
found := false
for _, e := range errs {
if e.Field == "Age" {
found = true
break
}
}
if !found {
t.Error("expected error for Age > 99")
}
}
F
function
TestValidator_ValidEmail
Parameters
t
pkg/validation/validator_test.go:66-79
func TestValidator_ValidEmail(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "x", Age: 25, Email: "notanemail"})
found := false
for _, e := range errs {
if e.Field == "Email" {
found = true
break
}
}
if !found {
t.Error("expected error for invalid email")
}
}
F
function
TestValidator_Pattern
Parameters
t
pkg/validation/validator_test.go:81-94
func TestValidator_Pattern(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "x", Age: 25, Email: "[email protected]", Label: "ABC"})
found := false
for _, e := range errs {
if e.Field == "Label" {
found = true
break
}
}
if !found {
t.Error("expected error for Label pattern mismatch")
}
}
F
function
TestValidator_AllValid
Parameters
t
pkg/validation/validator_test.go:96-102
func TestValidator_AllValid(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "Alice", Age: 25, Email: "[email protected]", Label: "abc"})
if len(errs) > 0 {
t.Errorf("expected no errors, got %v", errs)
}
}
F
function
TestValidator_EmptyRequired
Parameters
t
pkg/validation/validator_test.go:104-110
func TestValidator_EmptyRequired(t *testing.T)
{
v := New()
errs := v.Validate(user{Name: "", Age: 25, Email: "[email protected]"})
if len(errs) == 0 {
t.Error("expected error for required field")
}
}
F
function
TestValidator_NonStruct
Parameters
t
pkg/validation/validator_test.go:112-118
func TestValidator_NonStruct(t *testing.T)
{
v := New()
errs := v.Validate(42)
if len(errs) == 0 {
t.Error("expected error for non-struct")
}
}
F
function
TestValidator_ErrorsType_ErrorMethod
Parameters
t
pkg/validation/validator_test.go:120-125
func TestValidator_ErrorsType_ErrorMethod(t *testing.T)
{
errs := Errors{{Field: "x", Message: "bad"}}
if errs.Error() != "x: bad" {
t.Errorf("got %q", errs.Error())
}
}
F
function
TestValidator_OptionalInt
Parameters
t
pkg/validation/validator_test.go:127-133
func TestValidator_OptionalInt(t *testing.T)
{
v := New()
errs := v.Validate(optionalInt{Count: 0})
if len(errs) > 0 {
t.Error("zero int should be allowed for required since Go can't distinguish zero from unset")
}
}