-
Notifications
You must be signed in to change notification settings - Fork 6
/
validator.go
118 lines (95 loc) · 2.86 KB
/
validator.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
/*
Package validator provides a simple and extensible fields validation mechanism for structs and individual fields based on tags.
It wraps the package https://github.com/go-playground/validator and includes new custom validation rules and a simpler translation mechanism for errors.
*/
package validator
import (
"bytes"
"context"
"errors"
"fmt"
"html/template"
"strings"
vt "github.com/go-playground/validator/v10"
"go.uber.org/multierr"
)
// Validator contains the validator object fields.
type Validator struct {
// V is the validate object.
v *vt.Validate
// tpl contains the map of basic translation templates indexed by tag.
tpl map[string]*template.Template
}
// New returns a new validator with the specified options.
func New(opts ...Option) (*Validator, error) {
v := &Validator{v: vt.New()}
for _, applyOpt := range opts {
if err := applyOpt(v); err != nil {
return nil, err
}
}
return v, nil
}
// ValidateStruct validates the structure fields tagged with "validate" and returns a multierror.
func (v *Validator) ValidateStruct(obj any) error {
return v.ValidateStructCtx(context.Background(), obj)
}
// ValidateStructCtx validates the structure fields tagged with "validate" and returns a multierror.
func (v *Validator) ValidateStructCtx(ctx context.Context, obj any) error {
vErr := v.v.StructCtx(ctx, obj)
var (
valErr vt.ValidationErrors
err error
)
if errors.As(vErr, &valErr) {
for _, fe := range valErr {
// separate tags grouped by OR
tags := strings.Split(fe.Tag(), "|")
for _, tag := range tags {
if strings.HasPrefix(tag, "falseif") {
// the "falseif" tag only works in combination with other tags
continue
}
err = multierr.Append(err, v.tagError(fe, tag))
}
}
}
//nolint:wrapcheck
return err
}
// tagError set the error message associated with the validation tag.
func (v *Validator) tagError(fe vt.FieldError, tag string) error {
tagParts := strings.SplitN(tag, "=", 2)
tagKey := tagParts[0]
tagParam := fe.Param()
if len(tagParts) == 2 {
tagParam = tagParts[1]
}
namespace := fe.Namespace()
if idx := strings.Index(namespace, "."); idx != -1 {
namespace = namespace[idx+1:] // remove root struct name
}
ve := &Error{
Tag: tagKey,
Param: tagParam,
FullTag: tag,
Namespace: namespace,
StructNamespace: fe.StructNamespace(),
Field: fe.Field(),
StructField: fe.StructField(),
Kind: fe.Kind().String(),
Value: fe.Value(),
}
ve.Err = v.translate(ve)
return ve
}
// translate returns the error message associated with the tag.
func (v *Validator) translate(ve *Error) string {
if t, ok := v.tpl[ve.Tag]; ok {
var out bytes.Buffer
if err := t.Execute(&out, ve); err == nil {
return out.String()
}
}
return fmt.Sprintf("%s is invalid because fails the rule: '%s'", ve.Namespace, ve.FullTag)
}