example_context_with_recursion_test.go   A
last analyzed

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 61
dl 0
loc 116
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A validation_test.ItIsNotDeeperThan 0 2 1
A validation_test.Property.Validate 0 9 1
A validation_test.contextWithNextNestingLevel 0 7 2
A validation_test.NestingLimitConstraint.ValidateProperty 0 12 3
A validation_test.ValidProperty 0 12 4
A validation_test.ExampleValidator_Validate_usingContextWithRecursion 0 25 1
1
package validation_test
2
3
import (
4
	"context"
5
	"errors"
6
	"fmt"
7
8
	"github.com/muonsoft/validation"
9
	"github.com/muonsoft/validation/it"
10
	"github.com/muonsoft/validation/validator"
11
)
12
13
var ErrNestingLimitReached = errors.New("nesting limit reached")
14
15
// It is recommended to make a custom constraint to check for nesting limit.
16
type NestingLimitConstraint struct {
17
	limit int
18
}
19
20
func (c NestingLimitConstraint) ValidateProperty(ctx context.Context, validator *validation.Validator, property *Property) error {
21
	level, ok := ctx.Value(nestingLevelKey).(int)
22
	if !ok {
23
		// Don't forget to handle missing value.
24
		return fmt.Errorf("nesting level not found in context")
25
	}
26
27
	if level >= c.limit {
28
		return validator.CreateViolation(ctx, ErrNestingLimitReached, "Maximum nesting level reached.")
29
	}
30
31
	return nil
32
}
33
34
func ItIsNotDeeperThan(limit int) NestingLimitConstraint {
35
	return NestingLimitConstraint{limit: limit}
36
}
37
38
// Properties can be nested.
39
type Property struct {
40
	Name       string
41
	Properties []Property
42
}
43
44
// You can declare you own constraint interface to create custom constraints.
45
type PropertyConstraint interface {
46
	ValidateProperty(ctx context.Context, validator *validation.Validator, property *Property) error
47
}
48
49
// To create your own functional argument for validation simply create a function with
50
// a typed value and use the validation.NewArgument constructor.
51
func ValidProperty(property *Property, constraints ...PropertyConstraint) validation.ValidatorArgument {
52
	return validation.NewArgument(func(ctx context.Context, validator *validation.Validator) (*validation.ViolationList, error) {
53
		violations := validation.NewViolationList()
54
55
		for i := range constraints {
56
			err := violations.AppendFromError(constraints[i].ValidateProperty(ctx, validator, property))
57
			if err != nil {
58
				return nil, err
59
			}
60
		}
61
62
		return violations, nil
63
	})
64
}
65
66
type recursionKey string
67
68
const nestingLevelKey recursionKey = "nestingLevel"
69
70
func (p Property) Validate(ctx context.Context, validator *validation.Validator) error {
71
	return validator.Validate(
72
		// Incrementing nesting level in context with special function.
73
		contextWithNextNestingLevel(ctx),
74
		// Executing validation for maximum nesting level of properties.
75
		ValidProperty(&p, ItIsNotDeeperThan(3)),
76
		validation.StringProperty("name", p.Name, it.IsNotBlank()),
77
		// This should run recursive validation for properties.
78
		validation.ValidSliceProperty("properties", p.Properties),
79
	)
80
}
81
82
// This function increments current nesting level.
83
func contextWithNextNestingLevel(ctx context.Context) context.Context {
84
	level, ok := ctx.Value(nestingLevelKey).(int)
85
	if !ok {
86
		level = -1
87
	}
88
89
	return context.WithValue(ctx, nestingLevelKey, level+1)
90
}
91
92
func ExampleValidator_Validate_usingContextWithRecursion() {
93
	properties := []Property{
94
		{
95
			Name: "top",
96
			Properties: []Property{
97
				{
98
					Name: "middle",
99
					Properties: []Property{
100
						{
101
							Name: "low",
102
							Properties: []Property{
103
								// This property should cause a violation.
104
								{Name: "limited"},
105
							},
106
						},
107
					},
108
				},
109
			},
110
		},
111
	}
112
113
	err := validator.Validate(context.Background(), validation.ValidSlice(properties))
114
115
	fmt.Println(err)
116
	fmt.Println("errors.Is(err, ErrNestingLimitReached) =", errors.Is(err, ErrNestingLimitReached))
117
	// Output:
118
	// violation at "[0].properties[0].properties[0].properties[0]": "Maximum nesting level reached."
119
	// errors.Is(err, ErrNestingLimitReached) = true
120
}
121