-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidator.go
More file actions
110 lines (98 loc) · 2.47 KB
/
validator.go
File metadata and controls
110 lines (98 loc) · 2.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package validator
import (
"errors"
"fmt"
"reflect"
)
type validatorElem struct {
tag string
validator validatorI
}
var validators = []validatorElem{
{tag: "env", validator: envV{}},
{tag: "default", validator: defaultV{}},
{tag: "flags", validator: flagsV{}},
{tag: "min", validator: minV{}},
{tag: "max", validator: maxV{}},
{tag: "regex", validator: regexV{}},
}
type validator struct {
err error
}
func Validate[T any](strct *T) error {
v := validator{}
return v.validateStruct("", strct)
}
// strct needs to be pointer to struct
func (v *validator) validateStruct(baseFieldPath string, strct any) error {
rv := reflect.ValueOf(strct).Elem()
// needs to resolve interface to call FieldByName
if rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
// needs to resolve pointer to call FieldByName
if rv.Kind() == reflect.Pointer {
rv = rv.Elem()
}
rt := rv.Type()
// rt has to be kind struct
if rt.Kind() != reflect.Struct {
panic(fmt.Sprintf("validator: cannot validate type %s", rt.Kind()))
}
defaultField, ok := rt.FieldByName("_")
var defaultTags map[string]string // tag name -> tag value
if ok {
defaultTags = map[string]string{}
for _, elem := range validators {
if val, ok := defaultField.Tag.Lookup(elem.tag); ok {
defaultTags[elem.tag] = val
}
}
}
for i := 0; i < rt.NumField(); i++ {
fieldT := rt.Field(i)
if fieldT.Name == "_" {
continue
}
fieldV := rv.Field(i)
resolved := fieldV
if resolved.Kind() == reflect.Pointer {
resolved = resolved.Elem()
}
if resolved.Kind() == reflect.Struct {
// ignore err, its already in v.err
_ = v.validateStruct(
fmt.Sprintf("%s%s.", baseFieldPath, fieldT.Name),
resolved.Addr().Interface(),
)
}
// validate field
for _, elem := range validators {
if _, ok := defaultTags[elem.tag]; ok {
// will be validated by default tags, no need to do it now
continue
}
val, ok := fieldT.Tag.Lookup(elem.tag)
if !ok {
continue
}
if err := elem.validator.validate(val, fieldV); err != nil {
v.err = errors.Join(v.err, fmt.Errorf("%s%s: %s", baseFieldPath, fieldT.Name, err.Error()))
}
}
// validate default tags
for _, elem := range validators {
val, ok := defaultTags[elem.tag]
if !ok {
continue
}
if err := elem.validator.validate(val, fieldV); err != nil {
v.err = errors.Join(v.err, fmt.Errorf("%s%s: %s", baseFieldPath, fieldT.Name, err.Error()))
}
}
}
if v.err != nil {
return v.err
}
return nil
}