-
Notifications
You must be signed in to change notification settings - Fork 35
/
validators.go
431 lines (369 loc) · 11.9 KB
/
validators.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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
package validation
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
)
var validate *validator.Validate
type Action string
const (
UNKNOWN Action = ""
SKIP Action = "SKIP"
FAIL Action = "FAIL"
OMITEMPTY Action = "OMITEMPTY"
NOT_EMPTY Action = "NOT_EMPTY"
)
// Validator represents a validation rule.
type Validator struct {
Tags string
Err string
ignore bool
action Action
}
// None is an empty validator that does nothing (is skipped).
// It is useful for custom validators.
var None Validator = Validator{}
var customValidators = make(map[string]Validator)
// RegisterCustomValidator registers custom validator with custom key.
func RegisterCustomValidator(key string, v Validator) {
customValidators[key] = v
}
// RemoveCustomValidator removes custom validator from a list.
func RemoveCustomValidator(key string) {
delete(customValidators, key)
}
// RegisterCustomValidator registers custom validator with custom key.
func ClearCustomValidators() {
customValidators = make(map[string]Validator)
}
// initialize creates new singleton validator if its value is nil.
func initialize() {
if validate != nil {
return
}
validate = validator.New()
validate.RegisterTagNameFunc(fieldName)
validate.RegisterValidation("extra_alphanumhyp", extra_AlphaNumericHyphen)
validate.RegisterValidation("extra_alphanumhypus", extra_AlphaNumericHyphenUnderscore)
validate.RegisterValidation("extra_vsemver", extra_VSemVer)
validate.RegisterValidation("extra_ipinrange", extra_IPInRange)
validate.RegisterValidation("extra_uniquefield", extra_UniqueField)
validate.RegisterValidation("extra_regexany", extra_RegexAny)
validate.RegisterValidation("extra_regexall", extra_RegexAll)
}
// validate validates the provided value against the validator.
// It returns encountered validation errors and boolean indicating
// whether to skip further validation of a field or not.
func (v *Validator) validate(value interface{}) (ValidationErrors, bool) {
initialize()
if v.ignore {
return nil, false
}
switch v.action {
case SKIP:
return nil, true
case FAIL:
return v.ToError(), false
case OMITEMPTY:
return nil, isEmpty(value)
case NOT_EMPTY:
if isEmpty(value) {
return v.ToError(), false
} else {
return nil, false
}
}
errs := validate.Var(value, v.Tags)
es := toValidationErrors(errs)
if len(v.Err) > 0 {
for i := range es {
es[i].Err = v.Err
}
}
return es, false
}
func (v Validator) ToError() ValidationErrors {
return ValidationErrors{
{
Tag: v.Tags,
ActualTag: v.Tags,
Err: v.Err,
},
}
}
// Error overwrites the validator's default error message with the user-defined error
// and returns the modified validator.
func (v Validator) Error(err string) Validator {
v.Err = err
return v
}
// Errorf overwrites the validator's default error message with the formatted user-defined
// error and returns the modified validator.
func (v Validator) Errorf(err string, opt ...interface{}) Validator {
v.Err = fmt.Sprintf(err, opt...)
return v
}
// When allows validator to be applied only when the given condition is met.
func (v Validator) When(condition bool) Validator {
v.ignore = !condition
return v
}
// Custom returns custom validator registered with the given key.
func Custom(key string) Validator {
return customValidators[key]
}
// Tags returns a new validator with the given tags. It is a generic validator that
// allows use of any validation rule from 'github.com/go-playground/validator' library.
func Tags(tags string) Validator {
return Validator{
Tags: tags,
}
}
// OmitEmpty prevents further validation of the field, if the field is empty.
func OmitEmpty() Validator {
return Validator{
Tags: "omitempty",
action: OMITEMPTY,
}
}
// Skip prevents further validation of the field.
func Skip() Validator {
return Validator{
Tags: "-",
action: SKIP,
}
}
// Fail triggers validation error.
func Fail() Validator {
return Validator{
Tags: "fail",
Err: "Field '{.Field}' (forcefully) failed validation.",
action: FAIL,
}
}
// Required validator verifies that value is provided.
func Required() Validator {
return Validator{
Tags: "required",
Err: "Field '{.Field}' is required.",
}
}
// NotEmpty validator checks whether value is not blank or with zero length.
func NotEmpty() Validator {
return Validator{
Tags: "notempty",
Err: "Field '{.Field}' is required and cannot be empty.",
action: NOT_EMPTY,
}
}
// Unique validator checks whether all elements within array, slice or map are unique.
func Unique() Validator {
return Validator{
Tags: "unique",
Err: "All elements within '{.Field}' must be unique.",
}
}
// Unique validator checks whether the given field is unique for all elements within
// a slice of struct.
func UniqueField(field string) Validator {
return Validator{
Tags: fmt.Sprintf("extra_uniquefield=%s", field),
Err: "Field '{.Param}' must be unique for each element in '{.Field}'.",
}
}
// Min checks whether the field value is greater than or equal to the specified value.
// In case of strings, slices, arrays and maps the length is checked.
func Min(value int) Validator {
return Validator{
Tags: fmt.Sprintf("min=%d", value),
Err: "Minimum value for field '{.Field}' is {.Param} (actual: {.Value}).",
}
}
// Max checks whether the field value is less than or equal to the specified value.
// In case of strings, slices, arrays and maps the length is checked.
func Max(value int) Validator {
return Validator{
Tags: fmt.Sprintf("max=%d", value),
Err: "Maximum value for field '{.Field}' is {.Param} (actual: {.Value}).",
}
}
// Len checks if the field length matches the specified value.
func Len(value int) Validator {
return Validator{
Tags: fmt.Sprintf("len=%d", value),
Err: "Length of the field '{.Field}' must be {.Param} (actual: {.Value}).",
}
}
// MinLen checks whether the field length is greater than or equal to the specified value.
func MinLen(value int) Validator {
return Min(value).Error("Minimum length of the field '{.Field}' is {.Param} (actual: {.Value})")
}
// MaxLen checks whether the field length is less than or equal to the specified value.
func MaxLen(value int) Validator {
return Max(value).Error("Maximum length of the field '{.Field}' is {.Param} (actual: {.Value})")
}
// IP checks whether the field value is a valid IP address.
func IP() Validator {
return Validator{
Tags: "ip",
Err: "Field '{.Field}' must be a valid IP address (actual: {.Value}).",
}
}
// IPv4 checks whether the field value is a valid v4 IP address.
func IPv4() Validator {
return Validator{
Tags: "ipv4",
Err: "Field '{.Field}' must be a valid IPv4 address (actual: {.Value}).",
}
}
// IPv6 checks whether the field value is a valid v6 IP address.
func IPv6() Validator {
return Validator{
Tags: "ipv6",
Err: "Field '{.Field}' must be a valid IPv6 address (actual: {.Value}).",
}
}
// CIDR checks whether the field value is a valid CIDR address.
func CIDR() Validator {
return Validator{
Tags: "cidr",
Err: "Field '{.Field}' must be a valid CIDR address (actual: {.Value}).",
}
}
// CIDRv4 checks whether the field value is a valid v4 CIDR address.
func CIDRv4() Validator {
return Validator{
Tags: "cidrv4",
Err: "Field '{.Field}' must be a valid CIDRv4 address (actual: {.Value}).",
}
}
// CIDRv6 checks whether the field value is a valid v6 CIDR address.
func CIDRv6() Validator {
return Validator{
Tags: "cidrv6",
Err: "Field '{.Field}' must be a valid CIDRv6 address (actual: {.Value}).",
}
}
// IPInRange checks whether the field value is contained within the specified CIDR.
func IPInRange(cidr string) Validator {
return Validator{
Tags: fmt.Sprintf("extra_ipinrange=%s", cidr),
Err: fmt.Sprintf("Field '{.Field}' must be a valid IP address within '{.Param}' subnet. (actual: {.Value})"),
}
}
// MAC checks whether the field value is a valid MAC address.
func MAC() Validator {
return Validator{
Tags: "mac",
Err: "Field '{.Field}' must be a valid MAC address (actual: {.Value}).",
}
}
// OneOf checks whether the field value equals one of the specified values.
// If no value is provided, the validation always fails.
func OneOf[T any](values ...T) Validator {
var s []string
for _, v := range values {
s = append(s, toString(v))
}
oneOf := strings.Join(s, " ")
valid := strings.Join(s, "|")
return Validator{
Tags: fmt.Sprintf("oneof=%s", oneOf),
Err: fmt.Sprintf("Field '{.Field}' must be one of the following values: [%s] (actual: {.Value}).", valid),
}
}
// Alpha checks whether the field contains only ASCII alpha characters.
func Alpha() Validator {
return Validator{
Tags: "alpha",
Err: "Field '{.Field}' can contain only alpha characters (a-Z). (actual: {.Value})",
}
}
// Numeric checks whether the field contains only numeric characters.
// Validation fails for integers and floats.
func Numeric() Validator {
return Validator{
Tags: "numeric",
Err: "Field '{.Field}' can contain only numeric characters (0-9). (actual: {.Value})",
}
}
// AlphaNumeric checks whether the field contains only alphanumeric characters.
// Validation fails for integers and floats.
func AlphaNumeric() Validator {
return Validator{
Tags: "alphanum",
Err: "Field '{.Field}' can contain only alphanumeric characters. (actual: {.Value})",
}
}
// AlphaNumericDash checks whether the field contains only alphanumeric characters
// (a-Z0-9) and hyphen (-). Validation fails non string values.
func AlphaNumericHyp() Validator {
return Validator{
Tags: "extra_alphanumhyp",
Err: "Field '{.Field}' can contain only alphanumeric characters and hyphen. (actual: {.Value})",
}
}
// AlphaNumericDash checks whether the field contains only alphanumeric characters
// (a-Z0-9), hyphen (-) and underscore (_). Validation fails non string values.
func AlphaNumericHypUS() Validator {
return Validator{
Tags: "extra_alphanumhypus",
Err: "Field '{.Field}' can contain only alphanumeric characters, hyphen and underscore. (actual: {.Value})",
}
}
// Lowercase checks whether the field contains only lowercase characters.
func Lowercase() Validator {
return Validator{
Tags: "lowercase",
Err: "Field '{.Field}' can contain only lowercase characters. (actual: {.Value})",
}
}
// Uppercase checks whether the field contains only uppercase characters.
func Uppercase() Validator {
return Validator{
Tags: "uppercase",
Err: "Field '{.Field}' can contain only uppercase characters. (actual: {.Value})",
}
}
// File checks whether the field is a valid file path and whether file exists.
func FileExists() Validator {
return Validator{
Tags: "file",
Err: "Field '{.Field}' must be a valid file path that points to an existing file. (actual: {.Value})",
}
}
// URL checks whether the field is a valid URL.
func URL() Validator {
return Validator{
Tags: "url",
Err: "Field '{.Field}' must be a valid URL. (actual: {.Value})",
}
}
// SemVer checks whether the field is a valid semantic version.
func SemVer() Validator {
return Validator{
Tags: "semver",
Err: "Field '{.Field}' must be a valid semantic version (e.g. 1.2.3). (actual: {.Value})",
}
}
// VSemVer checks whether the field is a valid semantic version and is prefixed with 'v'.
func VSemVer() Validator {
return Validator{
Tags: "extra_vsemver",
Err: "Field '{.Field}' must be a valid semantic version prefixed with 'v' (e.g. v1.2.3). (actual: {.Value})",
}
}
// Regex checks whether the field matches any regex expression.
func RegexAny(regex ...string) Validator {
return Validator{
Tags: fmt.Sprintf("extra_regexany=%s", strings.Join(regex, " ")),
Err: fmt.Sprintf("Field '{.Field}' does not match any regex expression %s. (actual: {.Value})", regex),
}
}
// Regex checks whether the field matches all regex expressions.
func RegexAll(regex ...string) Validator {
return Validator{
Tags: fmt.Sprintf("extra_regexall=%s", strings.Join(regex, " ")),
Err: fmt.Sprintf("Field '{.Field}' does not match all regex expressions %s. (actual: {.Value})", regex),
}
}