-
Notifications
You must be signed in to change notification settings - Fork 1
/
example_context_with_recursion_test.go
120 lines (99 loc) · 3.38 KB
/
example_context_with_recursion_test.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
package validation_test
import (
"context"
"errors"
"fmt"
"github.com/muonsoft/validation"
"github.com/muonsoft/validation/it"
"github.com/muonsoft/validation/validator"
)
var ErrNestingLimitReached = errors.New("nesting limit reached")
// It is recommended to make a custom constraint to check for nesting limit.
type NestingLimitConstraint struct {
limit int
}
func (c NestingLimitConstraint) ValidateProperty(ctx context.Context, validator *validation.Validator, property *Property) error {
level, ok := ctx.Value(nestingLevelKey).(int)
if !ok {
// Don't forget to handle missing value.
return fmt.Errorf("nesting level not found in context")
}
if level >= c.limit {
return validator.CreateViolation(ctx, ErrNestingLimitReached, "Maximum nesting level reached.")
}
return nil
}
func ItIsNotDeeperThan(limit int) NestingLimitConstraint {
return NestingLimitConstraint{limit: limit}
}
// Properties can be nested.
type Property struct {
Name string
Properties []Property
}
// You can declare you own constraint interface to create custom constraints.
type PropertyConstraint interface {
ValidateProperty(ctx context.Context, validator *validation.Validator, property *Property) error
}
// To create your own functional argument for validation simply create a function with
// a typed value and use the validation.NewArgument constructor.
func ValidProperty(property *Property, constraints ...PropertyConstraint) validation.ValidatorArgument {
return validation.NewArgument(func(ctx context.Context, validator *validation.Validator) (*validation.ViolationList, error) {
violations := validation.NewViolationList()
for i := range constraints {
err := violations.AppendFromError(constraints[i].ValidateProperty(ctx, validator, property))
if err != nil {
return nil, err
}
}
return violations, nil
})
}
type recursionKey string
const nestingLevelKey recursionKey = "nestingLevel"
func (p Property) Validate(ctx context.Context, validator *validation.Validator) error {
return validator.Validate(
// Incrementing nesting level in context with special function.
contextWithNextNestingLevel(ctx),
// Executing validation for maximum nesting level of properties.
ValidProperty(&p, ItIsNotDeeperThan(3)),
validation.StringProperty("name", p.Name, it.IsNotBlank()),
// This should run recursive validation for properties.
validation.ValidSliceProperty("properties", p.Properties),
)
}
// This function increments current nesting level.
func contextWithNextNestingLevel(ctx context.Context) context.Context {
level, ok := ctx.Value(nestingLevelKey).(int)
if !ok {
level = -1
}
return context.WithValue(ctx, nestingLevelKey, level+1)
}
func ExampleValidator_Validate_usingContextWithRecursion() {
properties := []Property{
{
Name: "top",
Properties: []Property{
{
Name: "middle",
Properties: []Property{
{
Name: "low",
Properties: []Property{
// This property should cause a violation.
{Name: "limited"},
},
},
},
},
},
},
}
err := validator.Validate(context.Background(), validation.ValidSlice(properties))
fmt.Println(err)
fmt.Println("errors.Is(err, ErrNestingLimitReached) =", errors.Is(err, ErrNestingLimitReached))
// Output:
// violation at "[0].properties[0].properties[0].properties[0]": "Maximum nesting level reached."
// errors.Is(err, ErrNestingLimitReached) = true
}