Passed
Pull Request — main (#54)
by Rushan
01:54
created

validation.ConditionalConstraint.SetUp   A

Complexity

Conditions 2

Size

Total Lines 6
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 6
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
// 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
	messageParameters TemplateParameterList
80
}
81
82
// NewCustomStringConstraint creates a new string constraint from a function with signature func(string) bool.
83
// Optional parameters can be used to set up constraint name (first parameter), violation code (second),
84
// message template (third). All other parameters are ignored.
85
func NewCustomStringConstraint(isValid func(string) bool, parameters ...string) CustomStringConstraint {
86 1
	constraint := CustomStringConstraint{
87
		isValid:         isValid,
88
		name:            "CustomStringConstraint",
89
		code:            code.NotValid,
90
		messageTemplate: message.NotValid,
91
	}
92
93 1
	if len(parameters) > 0 {
94 1
		constraint.name = parameters[0]
95
	}
96 1
	if len(parameters) > 1 {
97 1
		constraint.code = parameters[1]
98
	}
99 1
	if len(parameters) > 2 {
100 1
		constraint.messageTemplate = parameters[2]
101
	}
102
103 1
	return constraint
104
}
105
106
// SetUp always returns no error.
107
func (c CustomStringConstraint) SetUp() error {
108 1
	return nil
109
}
110
111
// Name is the constraint name. It can be set via first parameter of function NewCustomStringConstraint.
112
func (c CustomStringConstraint) Name() string {
113 1
	return c.name
114
}
115
116
// Code overrides default code for produced violation.
117
func (c CustomStringConstraint) Code(code string) CustomStringConstraint {
118 1
	c.code = code
119 1
	return c
120
}
121
122
// Message sets the violation message template. You can set custom template parameters
123
// for injecting its values into the final message. Also, you can use default parameters:
124
//
125
//	{{ value }} - the current (invalid) value.
126
func (c CustomStringConstraint) Message(template string, parameters ...TemplateParameter) CustomStringConstraint {
127 1
	c.messageTemplate = template
128 1
	c.messageParameters = parameters
129 1
	return c
130
}
131
132
// When enables conditional validation of this constraint. If the expression evaluates to false,
133
// then the constraint will be ignored.
134
func (c CustomStringConstraint) When(condition bool) CustomStringConstraint {
135 1
	c.isIgnored = !condition
136 1
	return c
137
}
138
139
func (c CustomStringConstraint) ValidateString(value *string, scope Scope) error {
140 1
	if c.isIgnored || value == nil || *value == "" || c.isValid(*value) {
141 1
		return nil
142
	}
143
144 1
	return scope.BuildViolation(c.code, c.messageTemplate).
145
		SetParameters(
146
			c.messageParameters.Prepend(
147
				TemplateParameter{Key: "{{ value }}", Value: *value},
148
			)...,
149
		).
150
		AddParameter("{{ value }}", *value).
151
		CreateViolation()
152
}
153
154
// ConditionalConstraint is used for conditional validation.
155
// Use the When function to initiate a conditional check.
156
// If the condition is true, then the constraints passed through the Then function will be applied.
157
// Otherwise, the constraints passed through the Else function will apply.
158
type ConditionalConstraint struct {
159
	condition       bool
160
	thenConstraints []Constraint
161
	elseConstraints []Constraint
162
}
163
164
// When function is used to initiate conditional validation.
165
// If the condition is true, then the constraints passed through the Then function will be applied.
166
// Otherwise, the constraints passed through the Else function will apply.
167
func When(condition bool) ConditionalConstraint {
168 1
	return ConditionalConstraint{condition: condition}
169
}
170
171
// Then function is used to set a sequence of constraints to be applied if the condition is true.
172
// If the list is empty error will be returned.
173
func (c ConditionalConstraint) Then(constraints ...Constraint) ConditionalConstraint {
174 1
	c.thenConstraints = constraints
175 1
	return c
176
}
177
178
// Else function is used to set a sequence of constraints to be applied if a condition is false.
179
func (c ConditionalConstraint) Else(constraints ...Constraint) ConditionalConstraint {
180 1
	c.elseConstraints = constraints
181 1
	return c
182
}
183
184
// Name is the constraint name.
185
func (c ConditionalConstraint) Name() string {
186 1
	return "ConditionalConstraint"
187
}
188
189
// SetUp will return an error if Then function did not set any constraints.
190
func (c ConditionalConstraint) SetUp() error {
191 1
	if len(c.thenConstraints) == 0 {
192 1
		return errThenBranchNotSet
193
	}
194
195 1
	return nil
196
}
197
198
func (c ConditionalConstraint) validate(
199
	scope Scope,
200
	validate ValidateByConstraintFunc,
201
) (*ViolationList, error) {
202 1
	violations := &ViolationList{}
203 1
	var constraints []Constraint
204
205 1
	if c.condition {
206 1
		constraints = c.thenConstraints
207
	} else {
208 1
		constraints = c.elseConstraints
209
	}
210
211 1
	for _, constraint := range constraints {
212 1
		err := violations.AppendFromError(validate(constraint, scope))
213 1
		if err != nil {
214
			return nil, err
215
		}
216
	}
217
218 1
	return violations, nil
219
}
220
221
// SequentialConstraint is used to set constraints allowing to interrupt the validation once
222
// the first violation is raised.
223
type SequentialConstraint struct {
224
	constraints []Constraint
225
}
226
227
// Sequentially function used to set of constraints that should be validated step-by-step.
228
// If the list is empty error will be returned.
229
func Sequentially(constraints ...Constraint) SequentialConstraint {
230 1
	return SequentialConstraint{constraints: constraints}
231
}
232
233
// Name is the constraint name.
234
func (c SequentialConstraint) Name() string {
235 1
	return "SequentialConstraint"
236
}
237
238
// SetUp will return an error if the list of constraints is empty.
239
func (c SequentialConstraint) SetUp() error {
240 1
	if len(c.constraints) == 0 {
241 1
		return errSequentiallyConstraintsNotSet
242
	}
243 1
	return nil
244
}
245
246
func (c SequentialConstraint) validate(
247
	scope Scope,
248
	validate ValidateByConstraintFunc,
249
) (*ViolationList, error) {
250 1
	violations := &ViolationList{}
251
252 1
	for _, constraint := range c.constraints {
253 1
		err := violations.AppendFromError(validate(constraint, scope))
254 1
		if err != nil {
255
			return nil, err
256 1
		} else if violations.len > 0 {
257 1
			return violations, nil
258
		}
259
	}
260
261
	return violations, nil
262
}
263
264
// AtLeastOneOfConstraint is used to set constraints allowing checks that the value satisfies
265
// at least one of the given constraints.
266
// The validation stops as soon as one constraint is satisfied.
267
type AtLeastOneOfConstraint struct {
268
	constraints []Constraint
269
}
270
271
// AtLeastOneOf function used to set of constraints that the value satisfies at least one of the given constraints.
272
// If the list is empty error will be returned.
273
func AtLeastOneOf(constraints ...Constraint) AtLeastOneOfConstraint {
274 1
	return AtLeastOneOfConstraint{constraints: constraints}
275
}
276
277
// Name is the constraint name.
278
func (c AtLeastOneOfConstraint) Name() string {
279 1
	return "AtLeastOneOfConstraint"
280
}
281
282
// SetUp will return an error if the list of constraints is empty.
283
func (c AtLeastOneOfConstraint) SetUp() error {
284 1
	if len(c.constraints) == 0 {
285 1
		return errAtLeastOneOfConstraintsNotSet
286
	}
287 1
	return nil
288
}
289
290
func (c AtLeastOneOfConstraint) validate(
291
	scope Scope,
292
	validate ValidateByConstraintFunc,
293
) (*ViolationList, error) {
294 1
	violations := &ViolationList{}
295
296 1
	for _, constraint := range c.constraints {
297 1
		err := violations.AppendFromError(validate(constraint, scope))
298 1
		if err != nil {
299
			return nil, err
300
		}
301
302 1
		if violations.len == 0 {
303 1
			return violations, nil
304
		}
305
	}
306
307 1
	return violations, nil
308
}
309
310
// CompoundConstraint is used to create your own set of reusable constraints, representing rules to use consistently.
311
type CompoundConstraint struct {
312
	constraints []Constraint
313
}
314
315
// NewCompoundConstraint function used to create set of reusable constraints.
316
// If the list is empty error will be returned.
317
func NewCompoundConstraint(constraints ...Constraint) CompoundConstraint {
318 1
	return CompoundConstraint{constraints: constraints}
319
}
320
321
// Name is the constraint name.
322
func (c CompoundConstraint) Name() string {
323
	return "CompoundConstraint"
324
}
325
326
// SetUp will return an error if the list of constraints is empty.
327
func (c CompoundConstraint) SetUp() error {
328 1
	if len(c.constraints) == 0 {
329
		return errCompoundConstraintsNotSet
330
	}
331 1
	return nil
332
}
333
334
func (c CompoundConstraint) validate(
335
	scope Scope,
336
	validate ValidateByConstraintFunc,
337
) (*ViolationList, error) {
338 1
	violations := &ViolationList{}
339
340 1
	for _, constraint := range c.constraints {
341 1
		err := violations.AppendFromError(validate(constraint, scope))
342 1
		if err != nil {
343
			return nil, err
344
		}
345
	}
346
347 1
	return violations, nil
348
}
349
350
type notFoundConstraint struct {
351
	key string
352
}
353
354
func (c notFoundConstraint) SetUp() error {
355 1
	return ConstraintNotFoundError{Key: c.key}
356
}
357
358
func (c notFoundConstraint) Name() string {
359 1
	return "notFoundConstraint"
360
}
361