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

validation.CustomStringConstraint.SetUp   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
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
// CustomStringConstraint can be used to create custom constraints for validating string values
68
// based on function with signature func(string) bool.
69
type CustomStringConstraint struct {
70
	isIgnored       bool
71
	isValid         func(string) bool
72
	name            string
73
	code            string
74
	messageTemplate string
75
}
76
77
// NewCustomStringConstraint creates a new string constraint from a function with signature func(string) bool.
78
// Optional parameters can be used to set up constraint name (first parameter), violation code (second),
79
// message template (third). All other parameters are ignored.
80
func NewCustomStringConstraint(isValid func(string) bool, parameters ...string) CustomStringConstraint {
81 1
	constraint := CustomStringConstraint{
82
		isValid:         isValid,
83
		name:            "CustomStringConstraint",
84
		code:            code.NotValid,
85
		messageTemplate: message.NotValid,
86
	}
87
88 1
	if len(parameters) > 0 {
89 1
		constraint.name = parameters[0]
90
	}
91 1
	if len(parameters) > 1 {
92 1
		constraint.code = parameters[1]
93
	}
94 1
	if len(parameters) > 2 {
95 1
		constraint.messageTemplate = parameters[2]
96
	}
97
98 1
	return constraint
99
}
100
101
// SetUp always returns no error.
102
func (c CustomStringConstraint) SetUp() error {
103 1
	return nil
104
}
105
106
// Name is the constraint name. It can be set via first parameter of function NewCustomStringConstraint.
107
func (c CustomStringConstraint) Name() string {
108 1
	return c.name
109
}
110
111
// Message sets the violation message template. You can use template parameters
112
// for injecting its values into the final message:
113
//
114
//	{{ value }} - the current (invalid) value.
115
func (c CustomStringConstraint) Message(message string) CustomStringConstraint {
116 1
	c.messageTemplate = message
117 1
	return c
118
}
119
120
// When enables conditional validation of this constraint. If the expression evaluates to false,
121
// then the constraint will be ignored.
122
func (c CustomStringConstraint) When(condition bool) CustomStringConstraint {
123 1
	c.isIgnored = !condition
124 1
	return c
125
}
126
127
func (c CustomStringConstraint) ValidateString(value *string, scope Scope) error {
128 1
	if c.isIgnored || value == nil || *value == "" || c.isValid(*value) {
129 1
		return nil
130
	}
131
132 1
	return scope.BuildViolation(c.code, c.messageTemplate).
133
		AddParameter("{{ value }}", *value).
134
		CreateViolation()
135
}
136
137
// ConditionalConstraint is used for conditional validation.
138
// Use the When function to initiate a conditional check.
139
// If the condition is true, then the constraints passed through the Then function will be applied.
140
// Otherwise, the constraints passed through the Else function will apply.
141
type ConditionalConstraint struct {
142
	condition       bool
143
	thenConstraints []Constraint
144
	elseConstraints []Constraint
145
}
146
147
// When function using to initiate a conditional check.
148
func When(condition bool) ConditionalConstraint {
149 1
	return ConditionalConstraint{
150
		condition: condition,
151
	}
152
}
153
154
// Then function applied the constraints if the condition is true.
155
//
156
// Example
157
//  v := "foo"
158
//	err := validator.ValidateString(
159
//		&value,
160
//		validation.When(true).
161
//		Then(
162
//			it.Matches(regexp.MustCompile(`^\\w$`)),
163
//		),
164
//	)
165
func (c ConditionalConstraint) Then(constraints ...Constraint) ConditionalConstraint {
166 1
	c.thenConstraints = constraints
167 1
	return c
168
}
169
170
// Else function applied the constraints if the condition is false.
171
//
172
// Example
173
//  v := "foo"
174
//	err := validator.ValidateString(
175
//		&value,
176
//		validation.When(false).
177
//		Then(
178
//			it.Matches(regexp.MustCompile(`^\\w$`)),
179
//		).
180
//		Else(
181
//			it.Matches(regexp.MustCompile(`^\\d$`)),
182
//		),
183
//	)
184
func (c ConditionalConstraint) Else(constraints ...Constraint) ConditionalConstraint {
185 1
	c.elseConstraints = constraints
186 1
	return c
187
}
188
189
// Name is the constraint name.
190
func (c ConditionalConstraint) Name() string {
191 1
	return "ConditionalConstraint"
192
}
193
194
// SetUp will return an error if the constraints to apply is empty.
195
func (c ConditionalConstraint) SetUp() error {
196 1
	if len(c.thenConstraints) == 0 {
197 1
		return errThenBranchNotSet
198
	}
199
200 1
	return nil
201
}
202
203
func (c ConditionalConstraint) validate(
204
	scope Scope,
205
	violations *ViolationList,
206
	validate ValidateByConstraintFunc,
207
) error {
208 1
	var constraints []Constraint
209 1
	if c.condition {
210 1
		constraints = c.thenConstraints
211
	} else {
212 1
		constraints = c.elseConstraints
213
	}
214
215 1
	for _, constraint := range constraints {
216 1
		err := violations.AppendFromError(validate(constraint, scope))
217 1
		if err != nil {
218
			return err
219
		}
220
	}
221
222 1
	return nil
223
}
224
225
// SequentiallyConstraint is used to set constraints allowing to interrupt the validation once
226
// the first violation is raised.
227
type SequentiallyConstraint struct {
228
	constraints []Constraint
229
}
230
231
// Sequentially function using to set of constraints that should be validated step-by-step.
232
func Sequentially(constraints ...Constraint) SequentiallyConstraint {
233 1
	return SequentiallyConstraint{
234
		constraints: constraints,
235
	}
236
}
237
238
// Name is the constraint name.
239
func (c SequentiallyConstraint) Name() string {
240 1
	return "SequentiallyConstraint"
241
}
242
243
// SetUp will return an error if the list of constraints is empty.
244
func (c SequentiallyConstraint) SetUp() error {
245 1
	if len(c.constraints) == 0 {
246 1
		return errSequentiallyConstraintsNotSet
247
	}
248 1
	return nil
249
}
250
251
func (c SequentiallyConstraint) validate(
252
	scope Scope,
253
	violations *ViolationList,
254
	validate ValidateByConstraintFunc,
255
) error {
256 1
	for _, constraint := range c.constraints {
257 1
		err := validate(constraint, scope)
258 1
		if err != nil {
259 1
			return violations.AppendFromError(err)
260
		}
261
	}
262
263
	return nil
264
}
265
266
type notFoundConstraint struct {
267
	key string
268
}
269
270
func (c notFoundConstraint) SetUp() error {
271 1
	return ConstraintNotFoundError{Key: c.key}
272
}
273
274
func (c notFoundConstraint) Name() string {
275 1
	return "notFoundConstraint"
276
}
277