-
Notifications
You must be signed in to change notification settings - Fork 0
/
validator.go
142 lines (130 loc) · 3.29 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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package validator
import (
"fmt"
"reflect"
"strings"
validation "github.com/go-playground/validator/v10"
"github.com/pkg/errors"
)
type Validator struct {
validate *validation.Validate
}
type Option func(*Validator)
func New(options ...Option) Validator {
validate := validation.New()
validator := Validator{validate: validate}
for _, option := range options {
option(&validator)
}
return validator
}
func (v Validator) Validate(payload interface{}) (Result, error) {
err := ensurePayloadIsSupported(payload)
if err != nil {
return Result{}, err
}
err = v.validate.Struct(payload)
switch typedErr := err.(type) {
case nil:
return Result{}, nil
case validation.ValidationErrors:
errors, err := convertValidationErrors(typedErr)
if err != nil {
return Result{}, err
}
return Result{Errors: errors}, nil
default:
return Result{}, errors.WithStack(err)
}
}
func ErrorFieldFromJSONTag() Option {
return func(validator *Validator) {
validator.validate.RegisterTagNameFunc(func(field reflect.StructField) string {
jsonTagValue := field.Tag.Get("json")
jsonTagParts := strings.SplitN(jsonTagValue, ",", 2)
jsonName := jsonTagParts[0]
return jsonName
})
}
}
func convertValidationErrors(validationErrors validation.ValidationErrors) ([]Error, error) {
var errors []Error
for _, validationError := range validationErrors {
error := convertValidationError(validationError)
errors = append(errors, error)
}
return errors, nil
}
var convertionFunctions = map[string]func(validation.FieldError) Error{
"required": func(validationError validation.FieldError) Error {
return Error{
Type: "MISSING",
Field: validationError.Field(),
}
},
"gte": func(validationError validation.FieldError) Error {
return Error{
Type: "TOO_LOW",
Field: validationError.Field(),
Value: validationError.Value(),
Details: map[string]interface{}{
"minimum": validationError.Param(),
},
}
},
}
func convertValidationError(validationError validation.FieldError) Error {
var error Error
tag := validationError.ActualTag()
switch tag {
case "required":
error = Error{
Type: "MISSING",
Field: validationError.Field(),
}
case "gte":
error = Error{
Type: "TOO_LOW",
Field: validationError.Field(),
Value: validationError.Value(),
Details: map[string]interface{}{
"minimum": validationError.Param(),
},
}
default:
panic(fmt.Sprintf("Unexpected validation tag '%s'", tag))
}
return error
}
func ensurePayloadIsSupported(payload interface{}) error {
payloadType := reflect.TypeOf(payload)
if payloadType.Kind() != reflect.Struct {
return ErrNonStructPayload
}
fieldCount := payloadType.NumField()
for fieldIndex := 0; fieldIndex < fieldCount; fieldIndex++ {
field := payloadType.Field(fieldIndex)
validateTagValue := field.Tag.Get("validate")
if validateTagValue == "" {
continue
}
validationRules := strings.Split(validateTagValue, ",")
for _, rule := range validationRules {
err := ensureRuleIsSupported(rule)
if err != nil {
return err
}
}
}
return nil
}
func ensureRuleIsSupported(rule string) error {
ruleParts := strings.SplitN(rule, "=", 2)
tag := ruleParts[0]
for supportedTag := range convertionFunctions {
if tag == supportedTag {
return nil
}
}
return ErrUnsupportedValidationTag{Tag: tag}
}