Test Failed
Push — main ( b26c62...faec64 )
by Igor
02:33
created

validation_test.NestingLimitConstraint.Name   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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