forked from BakedSoftware/go-validation
-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathvalidate.go
189 lines (152 loc) · 5.72 KB
/
validate.go
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/*
Package validate (go-validate) provides validations for struct fields based on a validation tag and offers additional validation functions.
*/
package validate
import (
"log"
"reflect"
"strings"
"sync"
)
// DefaultMap is the default validation map used to tell if a struct is valid.
var DefaultMap = Map{}
// Interface specifies the necessary methods a validation must implement to be compatible with this package
type Interface interface {
// SetFieldIndex stores the index of the field
SetFieldIndex(index int)
// FieldIndex retrieves the index of the field
FieldIndex() int
// SetFieldName stores the name of the field
SetFieldName(name string)
// FieldName retrieves the name of the field
FieldName() string
// Validate determines if the value is valid. The value nil is returned if it is valid
Validate(value interface{}, obj reflect.Value) *ValidationError
}
// Validation is an implementation of an Interface and can be used to provide basic functionality
// to a new validation type through an anonymous field
type Validation struct {
// Name of the validation
Name string
// fieldIndex is the field index location
fieldIndex int
// fieldName is the field name
fieldName string
}
// SetFieldIndex stores the index of the field the validation was applied to
func (v *Validation) SetFieldIndex(index int) {
v.fieldIndex = index
}
// FieldIndex retrieves the index of the field the validation was applied to
func (v *Validation) FieldIndex() int {
return v.fieldIndex
}
// SetFieldName stores the name of the field the validation was applied to
func (v *Validation) SetFieldName(name string) {
v.fieldName = name
}
// FieldName retrieves the name of the field the validation was applied to
func (v *Validation) FieldName() string {
return v.fieldName
}
// Validate determines if the value is valid. The value nil is returned if it is valid
func (v *Validation) Validate(_ interface{}, _ reflect.Value) *ValidationError {
return &ValidationError{
Key: v.fieldName,
Message: "validation not implemented",
}
}
// Map is an atomic validation map and when two sets happen at the same time, the latest that started wins.
type Map struct {
validator sync.Map // map[reflect.Type][]Interface
validationNameToBuilder sync.Map // map[string]func(string, reflect.Kind) (Interface, error)
}
// get will get the validator interface
func (m *Map) get(k reflect.Type) []Interface {
v, ok := m.validator.Load(k)
if !ok {
return []Interface{}
}
return v.([]Interface)
}
// set will store the validator interface
func (m *Map) set(k reflect.Type, v []Interface) {
m.validator.Store(k, v)
}
// AddValidation registers the validation specified by key to the known
// validations. If more than one validation registers with the same key, the
// last one will become the validation for that key.
func (m *Map) AddValidation(key string, fn func(string, reflect.Kind) (Interface, error)) {
m.validationNameToBuilder.Store(key, fn)
}
// IsValid will either store the builder interfaces or run the IsValid based on the reflection object type
func (m *Map) IsValid(object interface{}) (bool, []ValidationError) {
// Get the object's value and type
objectValue := reflect.ValueOf(object)
objectType := reflect.TypeOf(object)
// Get the validations
validations := m.get(objectType)
// If we are a pointer and not nil, run IsValid on it's interface
if objectValue.Kind() == reflect.Ptr && !objectValue.IsNil() {
return IsValid(objectValue.Elem().Interface())
}
// Do we have some validations?
if len(validations) == 0 {
var err error
// Loop the fields and decrement through the loop
for i := objectType.NumField() - 1; i >= 0; i-- {
field := objectType.Field(i)
validationTag := field.Tag.Get("validation")
// Do we have a validation tag?
if len(validationTag) > 0 {
validationComponent := strings.Split(validationTag, " ")
// Loop each validation component
for _, validationSpec := range validationComponent {
component := strings.Split(validationSpec, "=")
if len(component) != 2 {
log.Fatalln("invalid validation specification:", objectType.Name(), field.Name, validationSpec)
}
// Create the validation
var validation Interface
if builder, ok := m.validationNameToBuilder.Load(component[0]); ok && builder != nil {
fn := builder.(func(string, reflect.Kind) (Interface, error))
validation, err = fn(component[1], field.Type.Kind())
if err != nil {
log.Fatalln("error creating validation:", objectType.Name(), field.Name, validationSpec, err)
}
} else {
log.Fatalln("unknown validation named:", component[0])
}
// Store the other properties and append to validations
validation.SetFieldName(field.Name)
validation.SetFieldIndex(i)
validations = append(validations, validation)
}
}
}
// Set the validations
m.set(objectType, validations)
}
// Loop and build errors
var errors []ValidationError
for _, validation := range validations {
field := objectValue.Field(validation.FieldIndex())
value := field.Interface()
if err := validation.Validate(value, objectValue); err != nil {
errors = append(errors, *err)
}
}
// Return flag and errors
return len(errors) == 0, errors
}
// AddValidation registers the validation specified by key to the known
// validations. If more than one validation registers with the same key, the
// last one will become the validation for that key
// using DefaultMap.
func AddValidation(key string, fn func(string, reflect.Kind) (Interface, error)) {
DefaultMap.AddValidation(key, fn)
}
// IsValid determines if an object is valid based on its validation tags using DefaultMap.
func IsValid(object interface{}) (bool, []ValidationError) {
return DefaultMap.IsValid(object)
}