Passed
Push — main ( 3f29c3...92079a )
by Rushan
58s queued 10s
created

validation.SequentialConstraint.Name   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
crap 1
1
package validation
2
3
import (
4
	"github.com/muonsoft/validation/code"
5
	"github.com/muonsoft/validation/generic"
6
	"github.com/muonsoft/validation/message"
7
8
	"time"
9
)
10
11
// Constraint is the base interface to build validation constraints.
12
type Constraint interface {
13
	Option
14
	// Name is a constraint name that can be used in internal errors.
15
	Name() string
16
}
17
18
// NilConstraint is used for constraints that need to check value for nil. In common case
19
// you do not need to implement it in your constraints because nil values should be ignored.
20
type NilConstraint interface {
21
	Constraint
22
	ValidateNil(scope Scope) error
23
}
24
25
// BoolConstraint is used to build constraints for boolean values validation.
26
type BoolConstraint interface {
27
	Constraint
28
	ValidateBool(value *bool, scope Scope) error
29
}
30
31
// NumberConstraint is used to build constraints for numeric values validation.
32
//
33
// At this moment working with numbers is based on reflection.
34
// Be aware. This constraint is subject to be changed after generics implementation in Go.
35
type NumberConstraint interface {
36
	Constraint
37
	ValidateNumber(value generic.Number, scope Scope) error
38
}
39
40
// StringConstraint is used to build constraints for string values validation.
41
type StringConstraint interface {
42
	Constraint
43
	ValidateString(value *string, scope Scope) error
44
}
45
46
// IterableConstraint is used to build constraints for validation of iterables (arrays, slices, or maps).
47
//
48
// At this moment working with numbers is based on reflection.
49
// Be aware. This constraint is subject to be changed after generics implementation in Go.
50
type IterableConstraint interface {
51
	Constraint
52
	ValidateIterable(value generic.Iterable, scope Scope) error
53
}
54
55
// CountableConstraint is used to build constraints for simpler validation of iterable elements count.
56
type CountableConstraint interface {
57
	Constraint
58
	ValidateCountable(count int, scope Scope) error
59
}
60
61
// TimeConstraint is used to build constraints for date/time validation.
62
type TimeConstraint interface {
63
	Constraint
64
	ValidateTime(value *time.Time, scope Scope) error
65
}
66
67
type controlConstraint interface {
68
	validate(scope Scope, validate ValidateByConstraintFunc) (ViolationList, error)
69
}
70
71
// CustomStringConstraint can be used to create custom constraints for validating string values
72
// based on function with signature func(string) bool.
73
type CustomStringConstraint struct {
74
	isIgnored       bool
75
	isValid         func(string) bool
76
	name            string
77
	code            string
78
	messageTemplate string
79
}
80
81
// NewCustomStringConstraint creates a new string constraint from a function with signature func(string) bool.
82
// Optional parameters can be used to set up constraint name (first parameter), violation code (second),
83
// message template (third). All other parameters are ignored.
84
func NewCustomStringConstraint(isValid func(string) bool, parameters ...string) CustomStringConstraint {
85 1
	constraint := CustomStringConstraint{
86
		isValid:         isValid,
87
		name:            "CustomStringConstraint",
88
		code:            code.NotValid,
89
		messageTemplate: message.NotValid,
90
	}
91
92 1
	if len(parameters) > 0 {
93 1
		constraint.name = parameters[0]
94
	}
95 1
	if len(parameters) > 1 {
96 1
		constraint.code = parameters[1]
97
	}
98 1
	if len(parameters) > 2 {
99 1
		constraint.messageTemplate = parameters[2]
100
	}
101
102 1
	return constraint
103
}
104
105
// SetUp always returns no error.
106
func (c CustomStringConstraint) SetUp() error {
107 1
	return nil
108
}
109
110
// Name is the constraint name. It can be set via first parameter of function NewCustomStringConstraint.
111
func (c CustomStringConstraint) Name() string {
112 1
	return c.name
113
}
114
115
// Message sets the violation message template. You can use template parameters
116
// for injecting its values into the final message:
117
//
118
//	{{ value }} - the current (invalid) value.
119
func (c CustomStringConstraint) Message(message string) CustomStringConstraint {
120 1
	c.messageTemplate = message
121 1
	return c
122
}
123
124
// When enables conditional validation of this constraint. If the expression evaluates to false,
125
// then the constraint will be ignored.
126
func (c CustomStringConstraint) When(condition bool) CustomStringConstraint {
127 1
	c.isIgnored = !condition
128 1
	return c
129
}
130
131
func (c CustomStringConstraint) ValidateString(value *string, scope Scope) error {
132 1
	if c.isIgnored || value == nil || *value == "" || c.isValid(*value) {
133 1
		return nil
134
	}
135
136 1
	return scope.BuildViolation(c.code, c.messageTemplate).
137
		AddParameter("{{ value }}", *value).
138
		CreateViolation()
139
}
140
141
// ConditionalConstraint is used for conditional validation.
142
// Use the When function to initiate a conditional check.
143
// If the condition is true, then the constraints passed through the Then function will be applied.
144
// Otherwise, the constraints passed through the Else function will apply.
145
type ConditionalConstraint struct {
146
	condition       bool
147
	thenConstraints []Constraint
148
	elseConstraints []Constraint
149
}
150
151
// When function is used to initiate conditional validation.
152
// If the condition is true, then the constraints passed through the Then function will be applied.
153
// Otherwise, the constraints passed through the Else function will apply.
154
func When(condition bool) ConditionalConstraint {
155 1
	return ConditionalConstraint{condition: condition}
156
}
157
158
// Then function is used to set a sequence of constraints to be applied if the condition is true.
159
// If the list is empty error will be returned.
160
func (c ConditionalConstraint) Then(constraints ...Constraint) ConditionalConstraint {
161 1
	c.thenConstraints = constraints
162 1
	return c
163
}
164
165
// Else function is used to set a sequence of constraints to be applied if a condition is false.
166
func (c ConditionalConstraint) Else(constraints ...Constraint) ConditionalConstraint {
167 1
	c.elseConstraints = constraints
168 1
	return c
169
}
170
171
// Name is the constraint name.
172
func (c ConditionalConstraint) Name() string {
173 1
	return "ConditionalConstraint"
174
}
175
176
// SetUp will return an error if Then function did not set any constraints.
177
func (c ConditionalConstraint) SetUp() error {
178 1
	if len(c.thenConstraints) == 0 {
179 1
		return errThenBranchNotSet
180
	}
181
182 1
	return nil
183
}
184
185
func (c ConditionalConstraint) validate(
186
	scope Scope,
187
	validate ValidateByConstraintFunc,
188
) (ViolationList, error) {
189 1
	violations := make(ViolationList, 0)
190 1
	var constraints []Constraint
191
192 1
	if c.condition {
193 1
		constraints = c.thenConstraints
194
	} else {
195 1
		constraints = c.elseConstraints
196
	}
197
198 1
	for _, constraint := range constraints {
199 1
		err := violations.AppendFromError(validate(constraint, scope))
200 1
		if err != nil {
201
			return nil, err
202
		}
203
	}
204
205 1
	return violations, nil
206
}
207
208
// SequentialConstraint is used to set constraints allowing to interrupt the validation once
209
// the first violation is raised.
210
type SequentialConstraint struct {
211
	constraints []Constraint
212
}
213
214
// Sequentially function used to set of constraints that should be validated step-by-step.
215
// If the list is empty error will be returned.
216
func Sequentially(constraints ...Constraint) SequentialConstraint {
217 1
	return SequentialConstraint{constraints: constraints}
218
}
219
220
// Name is the constraint name.
221
func (c SequentialConstraint) Name() string {
222 1
	return "SequentialConstraint"
223
}
224
225
// SetUp will return an error if the list of constraints is empty.
226
func (c SequentialConstraint) SetUp() error {
227 1
	if len(c.constraints) == 0 {
228 1
		return errSequentiallyConstraintsNotSet
229
	}
230 1
	return nil
231
}
232
233
func (c SequentialConstraint) validate(
234
	scope Scope,
235
	validate ValidateByConstraintFunc,
236
) (ViolationList, error) {
237 1
	violations := make(ViolationList, 0)
238
239 1
	for _, constraint := range c.constraints {
240 1
		err := violations.AppendFromError(validate(constraint, scope))
241 1
		if err != nil {
242
			return nil, err
243 1
		} else if len(violations) > 0 {
244 1
			return violations, nil
245
		}
246
	}
247
248
	return violations, nil
249
}
250
251
type notFoundConstraint struct {
252
	key string
253
}
254
255
func (c notFoundConstraint) SetUp() error {
256 1
	return ConstraintNotFoundError{Key: c.key}
257
}
258
259
func (c notFoundConstraint) Name() string {
260 1
	return "notFoundConstraint"
261
}
262