-
Notifications
You must be signed in to change notification settings - Fork 19
/
validator.go
141 lines (119 loc) · 3.77 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
package utils
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"github.com/nyaruka/goflow/utils/jsonx"
"github.com/pkg/errors"
validator "gopkg.in/go-playground/validator.v9"
)
// Validator is our system validator, it can be shared across threads
var Validator = validator.New()
func init() {
// use JSON tags as field names in validation error messages
Validator.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "" {
return "-"
}
return name
})
Validator.RegisterAlias("http_method", "eq=GET|eq=HEAD|eq=POST|eq=PUT|eq=PATCH|eq=DELETE")
}
// ValidationErrors combines multiple validation errors as a single error
type ValidationErrors []error
// Error returns a string representation of these validation errors
func (e ValidationErrors) Error() string {
errs := make([]string, len(e))
for i := range e {
errs[i] = e[i].Error()
}
return strings.Join(errs, ", ")
}
// Validate will run validation on the given object and return a set of field specific errors in the format:
// field <fieldname> <tag specific message>
//
// For example: "field 'flows' is required"
//
func Validate(obj interface{}) error {
var err error
// gets the value stored in the interface var, and if it's a pointer, dereferences it
v := reflect.Indirect(reflect.ValueOf(obj))
if v.Type().Kind() == reflect.Slice {
err = Validator.Var(obj, `required,dive`)
} else {
err = Validator.Struct(obj)
}
if err == nil {
return nil
}
validationErrs, isValidationErr := err.(validator.ValidationErrors)
if !isValidationErr {
return err
}
newErrors := make([]error, len(validationErrs))
for i, fieldErr := range validationErrs {
location := fieldErr.Namespace()
// the first part of the namespace is always the struct name so we remove it
parts := strings.Split(location, ".")[1:]
// and ignore any parts called - as these come from composition
newParts := make([]string, 0)
for _, part := range parts {
if part != "-" {
newParts = append(newParts, part)
}
}
location = strings.Join(newParts, ".")
// generate a more user friendly description of the problem
var problem string
switch fieldErr.Tag() {
case "required":
problem = "is required"
case "uuid":
problem = "must be a valid UUID"
case "uuid4":
problem = "must be a valid UUID4"
case "url":
problem = "is not a valid URL"
case "min":
problem = fmt.Sprintf("must have a minimum of %s items", fieldErr.Param())
case "max":
problem = fmt.Sprintf("must have a maximum of %s items", fieldErr.Param())
case "mutually_exclusive":
problem = fmt.Sprintf("is mutually exclusive with '%s'", fieldErr.Param())
case "http_method":
problem = "is not a valid HTTP method"
case "msg_topic":
problem = "is not a valid message topic"
case "date_format":
problem = "is not a valid date format"
case "time_format":
problem = "is not a valid time format"
case "language":
problem = "is not a valid language code"
case "country":
problem = "is not a valid country code"
default:
problem = fmt.Sprintf("failed tag '%s'", fieldErr.Tag())
}
newErrors[i] = errors.Errorf("field '%s' %s", location, problem)
}
return ValidationErrors(newErrors)
}
// UnmarshalAndValidate is a convenience function to unmarshal an object and validate it
func UnmarshalAndValidate(data []byte, obj interface{}) error {
err := json.Unmarshal(data, obj)
if err != nil {
return err
}
return Validate(obj)
}
// UnmarshalAndValidateWithLimit unmarshals a struct with a limit on how many bytes can be read from the given reader
func UnmarshalAndValidateWithLimit(reader io.ReadCloser, s interface{}, limit int64) error {
if err := jsonx.UnmarshalWithLimit(reader, s, limit); err != nil {
return err
}
return Validate(s)
}