Passed
Push — main ( 8592e5...add42e )
by Rushan
55s queued 12s
created

validation_test.Property.Validate   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 1
dl 0
loc 10
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
	validation.Constraint
55
	ValidateProperty(property *Property, scope validation.Scope) error
56
}
57
58
// To create your own functional argument for validation simply create a function with
59
// a typed value and use the validation.NewArgument constructor.
60
func PropertyArgument(property *Property, options ...validation.Option) validation.Argument {
61
	return validation.NewArgument(options, func(constraint validation.Constraint, scope validation.Scope) error {
62
		if c, ok := constraint.(PropertyConstraint); ok {
63
			return c.ValidateProperty(property, scope)
64
		}
65
		// If you want to use built-in constraints for checking for nil or empty values
66
		// such as it.IsNil() or it.IsBlank().
67
		if c, ok := constraint.(validation.NilConstraint); ok {
68
			if property == nil {
69
				return c.ValidateNil(scope)
70
			}
71
			return nil
72
		}
73
74
		return validation.NewInapplicableConstraintError(constraint, "Property")
75
	})
76
}
77
78
type recursionKey string
79
80
const nestingLevelKey recursionKey = "nestingLevel"
81
82
func (p Property) Validate(validator *validation.Validator) error {
83
	// Incrementing nesting level in context with special function.
84
	ctx := contextWithNextNestingLevel(validator.Context())
85
86
	return validator.WithContext(ctx).Validate(
87
		// Executing validation for maximum nesting level of properties.
88
		PropertyArgument(&p, ItIsNotDeeperThan(3)),
89
		validation.StringProperty("name", &p.Name, it.IsNotBlank()),
90
		// This should run recursive validation for properties.
91
		validation.IterableProperty("properties", p.Properties),
92
	)
93
}
94
95
// This function increments current nesting level.
96
func contextWithNextNestingLevel(ctx context.Context) context.Context {
97
	level, ok := ctx.Value(nestingLevelKey).(int)
98
	if !ok {
99
		level = -1
100
	}
101
102
	return context.WithValue(ctx, nestingLevelKey, level+1)
103
}
104
105
func ExampleValidator_Context_usingContextWithRecursion() {
106
	properties := []Property{
107
		{
108
			Name: "top",
109
			Properties: []Property{
110
				{
111
					Name: "middle",
112
					Properties: []Property{
113
						{
114
							Name: "low",
115
							Properties: []Property{
116
								// This property should cause a violation.
117
								{Name: "limited"},
118
							},
119
						},
120
					},
121
				},
122
			},
123
		},
124
	}
125
126
	err := validator.ValidateIterable(properties)
127
128
	violations := err.(validation.ViolationList)
129
	for _, violation := range violations {
130
		fmt.Println(violation.Error())
131
	}
132
	// Output:
133
	// violation at '[0].properties[0].properties[0].properties[0]': Maximum nesting level reached.
134
}
135