Test Failed
Pull Request — main (#71)
by Igor
02:01
created

validation_test.ValidProperty   A

Complexity

Conditions 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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