Passed
Pull Request — main (#63)
by Igor
02:45
created

validation.SequentialConstraint.SetUp   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 2
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
// StringsConstraint is used to build constraints to validate an array or a slice of strings.
47
type StringsConstraint interface {
48
	Constraint
49
	ValidateStrings(values []string, scope Scope) error
50
}
51
52
// IterableConstraint is used to build constraints for validation of iterables (arrays, slices, or maps).
53
//
54
// At this moment working with numbers is based on reflection.
55
// Be aware. This constraint is subject to be changed after generics implementation in Go.
56
type IterableConstraint interface {
57
	Constraint
58
	ValidateIterable(value generic.Iterable, scope Scope) error
59
}
60
61
// CountableConstraint is used to build constraints for simpler validation of iterable elements count.
62
type CountableConstraint interface {
63
	Constraint
64
	ValidateCountable(count int, scope Scope) error
65
}
66
67
// TimeConstraint is used to build constraints for date/time validation.
68
type TimeConstraint interface {
69
	Constraint
70
	ValidateTime(value *time.Time, scope Scope) error
71
}
72
73
type controlConstraint interface {
74
	validate(scope Scope, validate ValidateByConstraintFunc) (*ViolationList, error)
75
}
76
77
// CustomStringConstraint can be used to create custom constraints for validating string values
78
// based on function with signature func(string) bool.
79
type CustomStringConstraint struct {
80
	isIgnored         bool
81
	isValid           func(string) bool
82
	name              string
83
	code              string
84
	messageTemplate   string
85
	messageParameters TemplateParameterList
86
}
87
88
// NewCustomStringConstraint creates a new string constraint from a function with signature func(string) bool.
89
// Optional parameters can be used to set up constraint name (first parameter), violation code (second),
90
// message template (third). All other parameters are ignored.
91
func NewCustomStringConstraint(isValid func(string) bool, parameters ...string) CustomStringConstraint {
92 1
	constraint := CustomStringConstraint{
93
		isValid:         isValid,
94
		name:            "CustomStringConstraint",
95
		code:            code.NotValid,
96
		messageTemplate: message.NotValid,
97
	}
98
99 1
	if len(parameters) > 0 {
100 1
		constraint.name = parameters[0]
101
	}
102 1
	if len(parameters) > 1 {
103 1
		constraint.code = parameters[1]
104
	}
105 1
	if len(parameters) > 2 {
106 1
		constraint.messageTemplate = parameters[2]
107
	}
108
109 1
	return constraint
110
}
111
112
// SetUp always returns no error.
113
func (c CustomStringConstraint) SetUp() error {
114 1
	return nil
115
}
116
117
// Name is the constraint name. It can be set via first parameter of function NewCustomStringConstraint.
118
func (c CustomStringConstraint) Name() string {
119 1
	return c.name
120
}
121
122
// Code overrides default code for produced violation.
123
func (c CustomStringConstraint) Code(code string) CustomStringConstraint {
124 1
	c.code = code
125 1
	return c
126
}
127
128
// Message sets the violation message template. You can set custom template parameters
129
// for injecting its values into the final message. Also, you can use default parameters:
130
//
131
//	{{ value }} - the current (invalid) value.
132
func (c CustomStringConstraint) Message(template string, parameters ...TemplateParameter) CustomStringConstraint {
133 1
	c.messageTemplate = template
134 1
	c.messageParameters = parameters
135 1
	return c
136
}
137
138
// When enables conditional validation of this constraint. If the expression evaluates to false,
139
// then the constraint will be ignored.
140
func (c CustomStringConstraint) When(condition bool) CustomStringConstraint {
141 1
	c.isIgnored = !condition
142 1
	return c
143
}
144
145
func (c CustomStringConstraint) ValidateString(value *string, scope Scope) error {
146 1
	if c.isIgnored || value == nil || *value == "" || c.isValid(*value) {
147 1
		return nil
148
	}
149
150 1
	return scope.BuildViolation(c.code, c.messageTemplate).
151
		SetParameters(
152
			c.messageParameters.Prepend(
153
				TemplateParameter{Key: "{{ value }}", Value: *value},
154
			)...,
155
		).
156
		AddParameter("{{ value }}", *value).
157
		CreateViolation()
158
}
159
160
// ConditionalConstraint is used for conditional validation.
161
// Use the When function to initiate a conditional check.
162
// If the condition is true, then the constraints passed through the Then function will be applied.
163
// Otherwise, the constraints passed through the Else function will apply.
164
type ConditionalConstraint struct {
165
	condition       bool
166
	thenConstraints []Constraint
167
	elseConstraints []Constraint
168
}
169
170
// When function is used to initiate conditional validation.
171
// If the condition is true, then the constraints passed through the Then function will be applied.
172
// Otherwise, the constraints passed through the Else function will apply.
173
func When(condition bool) ConditionalConstraint {
174 1
	return ConditionalConstraint{condition: condition}
175
}
176
177
// Then function is used to set a sequence of constraints to be applied if the condition is true.
178
// If the list is empty error will be returned.
179
func (c ConditionalConstraint) Then(constraints ...Constraint) ConditionalConstraint {
180 1
	c.thenConstraints = constraints
181 1
	return c
182
}
183
184
// Else function is used to set a sequence of constraints to be applied if a condition is false.
185
func (c ConditionalConstraint) Else(constraints ...Constraint) ConditionalConstraint {
186 1
	c.elseConstraints = constraints
187 1
	return c
188
}
189
190
// Name is the constraint name.
191
func (c ConditionalConstraint) Name() string {
192 1
	return "ConditionalConstraint"
193
}
194
195
// SetUp will return an error if Then function did not set any constraints.
196
func (c ConditionalConstraint) SetUp() error {
197 1
	if len(c.thenConstraints) == 0 {
198 1
		return errThenBranchNotSet
199
	}
200
201 1
	return nil
202
}
203
204
func (c ConditionalConstraint) validate(
205
	scope Scope,
206
	validate ValidateByConstraintFunc,
207
) (*ViolationList, error) {
208 1
	violations := &ViolationList{}
209 1
	var constraints []Constraint
210
211 1
	if c.condition {
212 1
		constraints = c.thenConstraints
213
	} else {
214 1
		constraints = c.elseConstraints
215
	}
216
217 1
	for _, constraint := range constraints {
218 1
		err := violations.AppendFromError(validate(constraint, scope))
219 1
		if err != nil {
220
			return nil, err
221
		}
222
	}
223
224 1
	return violations, nil
225
}
226
227
// SequentialConstraint is used to set constraints allowing to interrupt the validation once
228
// the first violation is raised.
229
type SequentialConstraint struct {
230
	isIgnored   bool
231
	constraints []Constraint
232
}
233
234
// Sequentially function used to set of constraints that should be validated step-by-step.
235
// If the list is empty error will be returned.
236
func Sequentially(constraints ...Constraint) SequentialConstraint {
237 1
	return SequentialConstraint{constraints: constraints}
238
}
239
240
// Name is the constraint name.
241
func (c SequentialConstraint) Name() string {
242 1
	return "SequentialConstraint"
243
}
244
245
// SetUp will return an error if the list of constraints is empty.
246
func (c SequentialConstraint) SetUp() error {
247 1
	if len(c.constraints) == 0 {
248 1
		return errSequentiallyConstraintsNotSet
249
	}
250 1
	return nil
251
}
252
253
// When enables conditional validation of this constraint. If the expression evaluates to false,
254
// then the constraint will be ignored.
255
func (c SequentialConstraint) When(condition bool) SequentialConstraint {
256 1
	c.isIgnored = !condition
257 1
	return c
258
}
259
260
func (c SequentialConstraint) validate(
261
	scope Scope,
262
	validate ValidateByConstraintFunc,
263
) (*ViolationList, error) {
264 1
	if c.isIgnored {
265 1
		return nil, nil
266
	}
267
268 1
	violations := &ViolationList{}
269
270 1
	for _, constraint := range c.constraints {
271 1
		err := violations.AppendFromError(validate(constraint, scope))
272 1
		if err != nil {
273
			return nil, err
274 1
		} else if violations.len > 0 {
275 1
			return violations, nil
276
		}
277
	}
278
279
	return violations, nil
280
}
281
282
// AtLeastOneOfConstraint is used to set constraints allowing checks that the value satisfies
283
// at least one of the given constraints.
284
// The validation stops as soon as one constraint is satisfied.
285
type AtLeastOneOfConstraint struct {
286
	isIgnored   bool
287
	constraints []Constraint
288
}
289
290
// AtLeastOneOf function used to set of constraints that the value satisfies at least one of the given constraints.
291
// If the list is empty error will be returned.
292
func AtLeastOneOf(constraints ...Constraint) AtLeastOneOfConstraint {
293 1
	return AtLeastOneOfConstraint{constraints: constraints}
294
}
295
296
// Name is the constraint name.
297
func (c AtLeastOneOfConstraint) Name() string {
298 1
	return "AtLeastOneOfConstraint"
299
}
300
301
// SetUp will return an error if the list of constraints is empty.
302
func (c AtLeastOneOfConstraint) SetUp() error {
303 1
	if len(c.constraints) == 0 {
304 1
		return errAtLeastOneOfConstraintsNotSet
305
	}
306 1
	return nil
307
}
308
309
// When enables conditional validation of this constraint. If the expression evaluates to false,
310
// then the constraint will be ignored.
311
func (c AtLeastOneOfConstraint) When(condition bool) AtLeastOneOfConstraint {
312 1
	c.isIgnored = !condition
313 1
	return c
314
}
315
316
func (c AtLeastOneOfConstraint) validate(
317
	scope Scope,
318
	validate ValidateByConstraintFunc,
319
) (*ViolationList, error) {
320 1
	if c.isIgnored {
321 1
		return nil, nil
322
	}
323
324 1
	violations := &ViolationList{}
325
326 1
	for _, constraint := range c.constraints {
327 1
		violation := validate(constraint, scope)
328 1
		if violation == nil {
329 1
			return nil, nil
330
		}
331
332 1
		err := violations.AppendFromError(violation)
333 1
		if err != nil {
334
			return nil, err
335
		}
336
	}
337
338 1
	return violations, nil
339
}
340
341
// CompoundConstraint is used to create your own set of reusable constraints, representing rules to use consistently.
342
type CompoundConstraint struct {
343
	isIgnored   bool
344
	constraints []Constraint
345
}
346
347
// Compound function used to create set of reusable constraints.
348
// If the list is empty error will be returned.
349
func Compound(constraints ...Constraint) CompoundConstraint {
350 1
	return CompoundConstraint{constraints: constraints}
351
}
352
353
// Name is the constraint name.
354
func (c CompoundConstraint) Name() string {
355 1
	return "CompoundConstraint"
356
}
357
358
// SetUp will return an error if the list of constraints is empty.
359
func (c CompoundConstraint) SetUp() error {
360 1
	if len(c.constraints) == 0 {
361 1
		return errCompoundConstraintsNotSet
362
	}
363 1
	return nil
364
}
365
366
// When enables conditional validation of this constraint. If the expression evaluates to false,
367
// then the constraint will be ignored.
368
func (c CompoundConstraint) When(condition bool) CompoundConstraint {
369 1
	c.isIgnored = !condition
370 1
	return c
371
}
372
373
func (c CompoundConstraint) validate(
374
	scope Scope,
375
	validate ValidateByConstraintFunc,
376
) (*ViolationList, error) {
377 1
	if c.isIgnored {
378 1
		return nil, nil
379
	}
380
381 1
	violations := &ViolationList{}
382
383 1
	for _, constraint := range c.constraints {
384 1
		err := violations.AppendFromError(validate(constraint, scope))
385 1
		if err != nil {
386
			return nil, err
387
		}
388
	}
389
390 1
	return violations, nil
391
}
392
393
type notFoundConstraint struct {
394
	key string
395
}
396
397
func (c notFoundConstraint) SetUp() error {
398 1
	return ConstraintNotFoundError{Key: c.key}
399
}
400
401
func (c notFoundConstraint) Name() string {
402 1
	return "notFoundConstraint"
403
}
404