/
validation.go
134 lines (116 loc) · 3.22 KB
/
validation.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
package config
import (
"fmt"
"os"
"reflect"
"strings"
english "github.com/go-playground/locales/en"
"github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9"
"gopkg.in/go-playground/validator.v9/translations/en"
)
// Custom error for config validation
type configValidationError struct {
errList []string
}
func (err configValidationError) Error() string {
return strings.Join(err.errList, "\n")
}
func (err *configValidationError) addError(errStr string) {
err.errList = append(err.errList, errStr)
}
// Validator is a custom validator for configs
type Validator struct {
val *validator.Validate
config Base
trans ut.Translator
}
// NewValidator creates a new Validator
func NewValidator(config Base) *Validator {
result := validator.New()
// independent validators
result.RegisterValidation("dir", validateDir) // nolint: errcheck
result.RegisterValidation("file", validateFile) // nolint: errcheck
// default translations
eng := english.New()
uni := ut.New(eng, eng)
trans, _ := uni.GetTranslator("en")
err := en.RegisterDefaultTranslations(result, trans)
if err != nil {
panic(err)
}
// additional translations
translations := []struct {
tag string
translation string
}{
{
tag: "dir",
translation: fmt.Sprintf("{0} must point to an existing directory, but found '{1}'"),
},
{
tag: "file",
translation: fmt.Sprintf("{0} must point to an existing file, but found '{1}'"),
},
}
for _, t := range translations {
err = result.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation), translateFunc)
if err != nil {
panic(err)
}
}
return &Validator{
val: result,
config: config,
trans: trans,
}
}
func registrationFunc(tag string, translation string) validator.RegisterTranslationsFunc {
return func(ut ut.Translator) (err error) {
if err = ut.Add(tag, translation, true); err != nil {
return
}
return
}
}
func translateFunc(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field(), reflect.ValueOf(fe.Value()).String())
if err != nil {
return fe.(error).Error()
}
return t
}
// Validate validates config for errors and returns an error (it can be casted to
// configValidationError, containing a list of errors inside). When error is printed as string, it will
// automatically contains the full list of validation errors.
func (v *Validator) Validate() error {
// validate policy
err := v.val.Struct(v.config)
if err == nil {
return nil
}
// collect human-readable errors
result := configValidationError{}
vErrors := err.(validator.ValidationErrors) // nolint: errcheck
for _, vErr := range vErrors {
errStr := fmt.Sprintf("%s: %s", vErr.Namespace(), vErr.Translate(v.trans))
result.addError(errStr)
}
return result
}
// checks if a given string is an existing directory
func validateDir(fl validator.FieldLevel) bool {
path := fl.Field().String()
if stat, err := os.Stat(path); err == nil && stat.IsDir() {
return true
}
return false
}
// checks if a given string is an existing file
func validateFile(fl validator.FieldLevel) bool {
path := fl.Field().String()
if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
return true
}
return false
}