-
Notifications
You must be signed in to change notification settings - Fork 3
/
validationtool.go
92 lines (72 loc) · 2.9 KB
/
validationtool.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
package constraint
import (
"reflect"
"github.com/jt0/gomer/bind"
"github.com/jt0/gomer/flect"
"github.com/jt0/gomer/gomerr"
"github.com/jt0/gomer/structs"
)
var DefaultValidationTool = NewValidationTool(structs.StructTagDirectiveProvider{"validate"})
func Validate(v interface{}, validationTool *structs.Tool, optional ...*structs.ToolContext) gomerr.Gomerr {
return structs.ApplyTools(v, structs.EnsureContext(optional...), validationTool)
}
func NewValidationTool(dp structs.DirectiveProvider, optional ...TargetNamer) *structs.Tool {
var targetNamer TargetNamer
if len(optional) > 0 {
targetNamer = optional[0]
}
tool := structs.NewTool("constraint.ValidationTool", validationApplierProvider{targetNamer}, dp)
// TODO:p1 revisit - kinda hacky
built["struct"] = Struct(tool)
return tool
}
type validationApplierProvider struct {
targetNamer TargetNamer
}
func (ap validationApplierProvider) Applier(sv reflect.Type, sf reflect.StructField, directive string, _ string) (structs.Applier, gomerr.Gomerr) {
if directive == "" {
return nil, nil
}
c, ge := constraintFor(directive, none, sf)
if ge != nil {
return nil, gomerr.Configuration("Cannot process directive").Wrap(ge).AddAttribute("Directive", directive)
}
var target string
if ap.targetNamer != nil {
target = ap.targetNamer(sv, sf)
} else {
target = sf.Name
}
return validationApplier{target, c}, nil
}
// TargetNamer provides an alternative value for NotSatisfiedError.Target if an error occurs. By default, the value
// will be the field name, but one might want to have a camelCase value (using field.CamelCase) or pre-pend a value
// with an underscore include using field.CamelCase to change the casing on a field or pre-pending an underscore if
// desired.
type TargetNamer func(reflect.Type, reflect.StructField) string
// CamelCaseTargetNamer is a common alternative to rendering the field name in case of a validation error.
var CamelCaseTargetNamer = TransformFieldName(bind.CamelCase)
func TransformFieldName(transform func(string) string) TargetNamer {
return func(_ reflect.Type, sf reflect.StructField) string {
return transform(sf.Name)
}
}
type validationApplier struct {
target string
constraint Constraint
}
func (t validationApplier) Apply(sv reflect.Value, fv reflect.Value, _ *structs.ToolContext) gomerr.Gomerr {
if dc, ok := t.constraint.(*dynamicConstraint); ok {
for source, dv := range dc.dynamicValues {
if value, ge := structs.ValueFromStruct(sv, fv, source); ge != nil {
return gomerr.Configuration("Unable to validate").AddAttributes("Source", source, "Value", value).Wrap(ge)
} else if ge = flect.SetValue(dv.Elem(), value); ge != nil {
return gomerr.Configuration("Unable to validate").AddAttributes("Source", source, "Value", value).Wrap(ge)
}
}
}
if t.target == "_" {
return t.constraint.Validate(sv.Type().Name(), sv.Interface())
}
return t.constraint.Validate(t.target, fv.Interface())
}