-
Notifications
You must be signed in to change notification settings - Fork 1
/
yav.go
216 lines (176 loc) · 5.83 KB
/
yav.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package yav
type Validatable interface {
Validate() error
}
type Zeroer interface {
IsZero() bool
}
// ValidateFunc usually represents a single validation check against the given named value.
type ValidateFunc[T any] func(name string, value T) (stop bool, err error)
// Next is a no-op ValidateFunc.
func Next[T any](string, T) (stop bool, err error) {
return false, nil
}
// OmitEmpty skips further validation, when a generic comparable value is default for its type.
// Most of the validation packages contain specialized versions of this function, e.g. vstring.OmitEmpty, etc.
func OmitEmpty[T comparable](_ string, value T) (stop bool, err error) {
var zero T
return value == zero, nil
}
// Chain allows chaining validation funcs against a single struct field or value.
// If not nil, the result is always of Errors type.
func Chain[T any](name string, value T, validateFuncs ...ValidateFunc[T]) error {
var yavErrs Errors
for _, validateFunc := range validateFuncs {
stop, err := validateFunc(name, value)
yavErrs.Append(err)
if stop {
break
}
}
return yavErrs.AsError()
}
// Join returns combined Errors or nil. It is useful to combine Chain results, while validating multiple values.
func Join(errs ...error) error {
var yavErrs Errors
for _, err := range errs {
yavErrs.Append(err)
}
return yavErrs.AsError()
}
// Join2 exactly equals to Join with two arguments, but works faster.
func Join2(err0, err1 error) error {
var yavErrs Errors
yavErrs.Append(err0)
yavErrs.Append(err1)
return yavErrs.AsError()
}
// Join3 exactly equals to Join with three arguments, but works faster.
func Join3(err0, err1, err2 error) error {
var yavErrs Errors
yavErrs.Append(err0)
yavErrs.Append(err1)
yavErrs.Append(err2)
return yavErrs.AsError()
}
// Or combines the given validation funcs into a new one, which iterates over and sequentially invokes the arguments.
// When any of the functions returns a nil error, its result is immediately returned.
// Otherwise, a non-nil error and stop flag of the last function are returned.
//
// Constructing and dropping intermediate errors is somewhat expensive, so that use Or with care.
// If performance is crucial, write your own validation func returning an error if and only if all the checks fail.
func Or[T any](validateFuncs ...ValidateFunc[T]) ValidateFunc[T] {
if len(validateFuncs) == 1 {
return validateFuncs[0]
}
return func(name string, value T) (stop bool, err error) {
for _, validateFunc := range validateFuncs {
if stop, err = validateFunc(name, value); err == nil {
return
}
}
return
}
}
// Or2 exactly equals to Or with two arguments, but makes one less memory allocation.
func Or2[T any](validateFunc0, validateFunc1 ValidateFunc[T]) ValidateFunc[T] {
return func(name string, value T) (stop bool, err error) {
if stop, err = validateFunc0(name, value); err == nil {
return
}
return validateFunc1(name, value)
}
}
// Or3 exactly equals to Or with three arguments, but makes one less memory allocation.
func Or3[T any](validateFunc0, validateFunc1, validateFunc2 ValidateFunc[T]) ValidateFunc[T] {
return func(name string, value T) (stop bool, err error) {
if stop, err = validateFunc0(name, value); err == nil {
return
}
if stop, err = validateFunc1(name, value); err == nil {
return
}
return validateFunc2(name, value)
}
}
// Nested processes errors of either Error or Errors type, prepending Error.ValueName with name argument.
// It returns unsupported and nil errors as is.
func Nested(name string, err error) error {
if err == nil {
return nil
}
switch typedErr := err.(type) {
case Error:
return nestedYAV(name, typedErr)
case Errors:
for i, yavErr := range typedErr.Validation {
typedErr.Validation[i] = nestedYAV(name, yavErr)
}
return typedErr
default:
return err
}
}
func nestedYAV(name string, yavErr Error) Error {
if yavErr.ValueName == "" {
yavErr.ValueName = name
} else if yavErr.ValueName[0] == '[' {
yavErr.ValueName = name + yavErr.ValueName
} else {
yavErr.ValueName = name + "." + yavErr.ValueName
}
return yavErr
}
// UnnamedValidate is a ValidateFunc that simply calls Validatable.Validate of the given value.
// It may be useful, while validating slice items or map entries.
func UnnamedValidate[T Validatable](_ string, value T) (stop bool, err error) {
err = value.Validate()
return err != nil, err
}
// NestedValidate basically equals to UnnamedValidate, but additionally calls Nested before returning the error.
func NestedValidate[T Validatable](name string, value T) (stop bool, err error) {
err = value.Validate()
return err != nil, Nested(name, err)
}
// NamedCheck processes errors of either Error or Errors type,
// clearing Error.Parameter and replacing Error.CheckName with the given name.
// Unsupported and nil errors are returned as is.
func NamedCheck(checkName string, err error) error {
if err == nil {
return nil
}
switch typedErr := err.(type) {
case Error:
return namedCheckYAV(checkName, typedErr)
case Errors:
for i, yavErr := range typedErr.Validation {
typedErr.Validation[i] = namedCheckYAV(checkName, yavErr)
}
return typedErr
default:
return err
}
}
func namedCheckYAV(checkName string, yavErr Error) Error {
yavErr.CheckName = checkName
yavErr.Parameter = ""
return yavErr
}
// NamedCheckFunc combines the given validation funcs into a new named one.
// Those functions are invoked similarly to Chain, then NamedCheck is applied to the result.
func NamedCheckFunc[T any](checkName string, validateFuncs ...ValidateFunc[T]) ValidateFunc[T] {
if len(validateFuncs) == 0 {
return Next[T]
}
return func(name string, value T) (stop bool, err error) {
var yavErrs Errors
for _, validateFunc := range validateFuncs {
stop, err = validateFunc(name, value)
yavErrs.Append(err)
if stop {
break
}
}
return stop, NamedCheck(checkName, yavErrs.AsError())
}
}