Passed
Pull Request — main (#68)
by Igor
02:03
created

arguments.go   F

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
cc 68
eloc 175
dl 0
loc 384
ccs 103
cts 103
cp 1
crap 68
rs 2.96
c 0
b 0
f 0

41 Methods

Rating   Name   Duplication   Size   Complexity  
A validation.NilBoolProperty 0 2 1
A validation.NilBool 0 5 2
A validation.EachStringProperty 0 2 1
A validation.Iterable 0 10 3
A validation.*Arguments.addValidator 0 2 1
A validation.ValidProperty 0 2 1
A validation.CountableProperty 0 2 1
A validation.Time 0 5 2
A validation.NilTime 0 5 2
A validation.BoolProperty 0 2 1
A validation.Value 0 10 3
A validation.PropertyValue 0 2 1
A validation.EachString 0 5 2
A validation.Valid 0 5 2
A validation.argumentFunc.set 0 2 1
A validation.Strings 0 5 2
A validation.NilStringProperty 0 2 1
A validation.String 0 5 2
A validation.NilString 0 5 2
A validation.Number 0 10 3
A validation.NumberProperty 0 2 1
A validation.TimeProperty 0 2 1
A validation.StringsProperty 0 2 1
A validation.Countable 0 5 2
A validation.NilTimeProperty 0 2 1
A validation.Each 0 10 3
A validation.StringProperty 0 2 1
A validation.IterableProperty 0 2 1
A validation.Bool 0 5 2
A validation.EachProperty 0 2 1
A validation.Check 0 5 1
A validation.NewArgument 0 5 2
A validation.CheckNoViolations 0 13 4
A validation.CheckProperty 0 6 1
A validation.Language 0 5 2
A validation.Checker.When 0 3 1
A validation.Checker.set 0 3 1
A validation.Checker.Message 0 4 1
A validation.Checker.Code 0 3 1
A validation.Checker.WhenGroups 0 3 1
A validation.Checker.validate 0 13 5
1
package validation
2
3
import (
4
	"fmt"
5
	"time"
6
7
	"github.com/muonsoft/validation/code"
8
	"github.com/muonsoft/validation/generic"
9
	"github.com/muonsoft/validation/message"
10
	"golang.org/x/text/language"
11
)
12
13
// Argument used to set up the validation process. It is used to set up the current validation scope and to pass
14
// arguments for validation values.
15
type Argument interface {
16
	set(arguments *Arguments) error
17
}
18
19
type argumentFunc func(arguments *Arguments) error
20
21
func (f argumentFunc) set(arguments *Arguments) error {
22 1
	return f(arguments)
23
}
24
25
type Arguments struct {
26
	scope      Scope
27
	validators []validateFunc
28
}
29
30
func (args *Arguments) addValidator(validator validateFunc) {
31 1
	args.validators = append(args.validators, validator)
32
}
33
34
// Value argument is used to validate any supported value. It uses reflection to detect the type of the argument
35
// and pass it to a specific validation method.
36
//
37
// If the validator cannot determine the value or it is not supported, then NotValidatableError will be returned
38
// when calling the validator.Validate method.
39
func Value(value interface{}, options ...Option) Argument {
40 1
	return argumentFunc(func(arguments *Arguments) error {
41 1
		v, err := newValueValidator(value, options)
42 1
		if err != nil {
43 1
			return err
44
		}
45
46 1
		arguments.addValidator(v)
47
48 1
		return nil
49
	})
50
}
51
52
// PropertyValue argument is an alias for Value that automatically adds property name to the current scope.
53
func PropertyValue(name string, value interface{}, options ...Option) Argument {
54 1
	return Value(value, append([]Option{PropertyName(name)}, options...)...)
55
}
56
57
// Bool argument is used to validate boolean values.
58
func Bool(value bool, options ...Option) Argument {
59 1
	return argumentFunc(func(arguments *Arguments) error {
60 1
		arguments.addValidator(newBoolValidator(&value, options))
61
62 1
		return nil
63
	})
64
}
65
66
// BoolProperty argument is an alias for Bool that automatically adds property name to the current scope.
67
func BoolProperty(name string, value bool, options ...Option) Argument {
68 1
	return Bool(value, append([]Option{PropertyName(name)}, options...)...)
69
}
70
71
// NilBool argument is used to validate nillable boolean values.
72
func NilBool(value *bool, options ...Option) Argument {
73 1
	return argumentFunc(func(arguments *Arguments) error {
74 1
		arguments.addValidator(newBoolValidator(value, options))
75
76 1
		return nil
77
	})
78
}
79
80
// NilBoolProperty argument is an alias for NilBool that automatically adds property name to the current scope.
81
func NilBoolProperty(name string, value *bool, options ...Option) Argument {
82 1
	return NilBool(value, append([]Option{PropertyName(name)}, options...)...)
83
}
84
85
// Number argument is used to validate numbers (any types of integers or floats). At the moment it uses
86
// reflection to detect numeric value. Given value is internally converted into int64 or float64 to make comparisons.
87
//
88
// Warning! This method will be changed after generics implementation in Go.
89
func Number(value interface{}, options ...Option) Argument {
90 1
	return argumentFunc(func(arguments *Arguments) error {
91 1
		number, err := generic.NewNumber(value)
92 1
		if err != nil {
93 1
			return fmt.Errorf(`cannot convert value "%v" to number: %w`, value, err)
94
		}
95
96 1
		arguments.addValidator(newNumberValidator(*number, options))
97
98 1
		return nil
99
	})
100
}
101
102
// NumberProperty argument is an alias for Number that automatically adds property name to the current scope.
103
func NumberProperty(name string, value interface{}, options ...Option) Argument {
104 1
	return Number(value, append([]Option{PropertyName(name)}, options...)...)
105
}
106
107
// String argument is used to validate strings.
108
func String(value string, options ...Option) Argument {
109 1
	return argumentFunc(func(arguments *Arguments) error {
110 1
		arguments.addValidator(newStringValidator(&value, options))
111
112 1
		return nil
113
	})
114
}
115
116
// StringProperty argument is an alias for String that automatically adds property name to the current scope.
117
func StringProperty(name string, value string, options ...Option) Argument {
118 1
	return String(value, append([]Option{PropertyName(name)}, options...)...)
119
}
120
121
// NilString argument is used to validate nillable strings.
122
func NilString(value *string, options ...Option) Argument {
123 1
	return argumentFunc(func(arguments *Arguments) error {
124 1
		arguments.addValidator(newStringValidator(value, options))
125
126 1
		return nil
127
	})
128
}
129
130
// NilStringProperty argument is an alias for NilString that automatically adds property name to the current scope.
131
func NilStringProperty(name string, value *string, options ...Option) Argument {
132 1
	return NilString(value, append([]Option{PropertyName(name)}, options...)...)
133
}
134
135
// Strings argument is used to validate slice of strings.
136
func Strings(values []string, options ...Option) Argument {
137 1
	return argumentFunc(func(arguments *Arguments) error {
138 1
		arguments.addValidator(newStringsValidator(values, options))
139
140 1
		return nil
141
	})
142
}
143
144
// StringsProperty argument is an alias for Strings that automatically adds property name to the current scope.
145
func StringsProperty(name string, values []string, options ...Option) Argument {
146 1
	return Strings(values, append([]Option{PropertyName(name)}, options...)...)
147
}
148
149
// Iterable argument is used to validate arrays, slices, or maps. At the moment it uses reflection
150
// to iterate over values. So you can expect a performance hit using this method. For better performance
151
// it is recommended to make a custom type that implements the Validatable interface.
152
//
153
// Warning! This argument is subject to change in the final versions of the library.
154
func Iterable(value interface{}, options ...Option) Argument {
155 1
	return argumentFunc(func(arguments *Arguments) error {
156 1
		iterable, err := generic.NewIterable(value)
157 1
		if err != nil {
158 1
			return fmt.Errorf(`cannot convert value "%v" to iterable: %w`, value, err)
159
		}
160
161 1
		arguments.addValidator(newIterableValidator(iterable, options))
162
163 1
		return nil
164
	})
165
}
166
167
// IterableProperty argument is an alias for Iterable that automatically adds property name to the current scope.
168
func IterableProperty(name string, value interface{}, options ...Option) Argument {
169 1
	return Iterable(value, append([]Option{PropertyName(name)}, options...)...)
170
}
171
172
// Countable argument can be used to validate size of an array, slice, or map. You can pass result of len()
173
// function as an argument.
174
func Countable(count int, options ...Option) Argument {
175 1
	return argumentFunc(func(arguments *Arguments) error {
176 1
		arguments.addValidator(newCountableValidator(count, options))
177
178 1
		return nil
179
	})
180
}
181
182
// CountableProperty argument is an alias for Countable that automatically adds property name to the current scope.
183
func CountableProperty(name string, count int, options ...Option) Argument {
184 1
	return Countable(count, append([]Option{PropertyName(name)}, options...)...)
185
}
186
187
// Time argument is used to validate time.Time value.
188
func Time(value time.Time, options ...Option) Argument {
189 1
	return argumentFunc(func(arguments *Arguments) error {
190 1
		arguments.addValidator(newTimeValidator(&value, options))
191
192 1
		return nil
193
	})
194
}
195
196
// TimeProperty argument is an alias for Time that automatically adds property name to the current scope.
197
func TimeProperty(name string, value time.Time, options ...Option) Argument {
198 1
	return Time(value, append([]Option{PropertyName(name)}, options...)...)
199
}
200
201
// NilTime argument is used to validate nillable time.Time value.
202
func NilTime(value *time.Time, options ...Option) Argument {
203 1
	return argumentFunc(func(arguments *Arguments) error {
204 1
		arguments.addValidator(newTimeValidator(value, options))
205
206 1
		return nil
207
	})
208
}
209
210
// NilTimeProperty argument is an alias for NilTime that automatically adds property name to the current scope.
211
func NilTimeProperty(name string, value *time.Time, options ...Option) Argument {
212 1
	return NilTime(value, append([]Option{PropertyName(name)}, options...)...)
213
}
214
215
// Each is used to validate each value of iterable (array, slice, or map). At the moment it uses reflection
216
// to iterate over values. So you can expect a performance hit using this method. For better performance
217
// it is recommended to make a custom type that implements the Validatable interface. Also, you can use
218
// EachString argument to validate slice of strings.
219
//
220
// Warning! This argument is subject to change in the final versions of the library.
221
func Each(value interface{}, options ...Option) Argument {
222 1
	return argumentFunc(func(arguments *Arguments) error {
223 1
		iterable, err := generic.NewIterable(value)
224 1
		if err != nil {
225 1
			return fmt.Errorf(`cannot convert value "%v" to iterable: %w`, value, err)
226
		}
227
228 1
		arguments.addValidator(newEachValidator(iterable, options))
229
230 1
		return nil
231
	})
232
}
233
234
// EachProperty argument is an alias for Each that automatically adds property name to the current scope.
235
func EachProperty(name string, value interface{}, options ...Option) Argument {
236 1
	return Each(value, append([]Option{PropertyName(name)}, options...)...)
237
}
238
239
// EachString is used to validate a slice of strings. This is a more performant version of Each argument.
240
func EachString(values []string, options ...Option) Argument {
241 1
	return argumentFunc(func(arguments *Arguments) error {
242 1
		arguments.addValidator(newEachStringValidator(values, options))
243
244 1
		return nil
245
	})
246
}
247
248
// EachStringProperty argument is an alias for EachString that automatically adds property name to the current scope.
249
func EachStringProperty(name string, values []string, options ...Option) Argument {
250 1
	return EachString(values, append([]Option{PropertyName(name)}, options...)...)
251
}
252
253
// Valid is used to run validation on the Validatable type. This method is recommended
254
// to build a complex validation process.
255
func Valid(value Validatable, options ...Option) Argument {
256 1
	return argumentFunc(func(arguments *Arguments) error {
257 1
		arguments.addValidator(newValidValidator(value, options))
258
259 1
		return nil
260
	})
261
}
262
263
// ValidProperty argument is an alias for Valid that automatically adds property name to the current scope.
264
func ValidProperty(name string, value Validatable, options ...Option) Argument {
265 1
	return Valid(value, append([]Option{PropertyName(name)}, options...)...)
266
}
267
268
// CheckNoViolations is a special argument that checks err for violations. If err contains Violation or ViolationList
269
// then these violations will be appended into returned violation list from the validator. If err contains an error
270
// that does not implement an error interface, then the validation process will be terminated and
271
// this error will be returned.
272
func CheckNoViolations(err error) Argument {
273 1
	return argumentFunc(func(arguments *Arguments) error {
274 1
		arguments.addValidator(func(scope Scope) (*ViolationList, error) {
275 1
			violations := NewViolationList()
276 1
			fatal := violations.AppendFromError(err)
277 1
			if fatal != nil {
278 1
				return nil, fatal
279
			}
280
281 1
			return violations, nil
282
		})
283
284 1
		return nil
285
	})
286
}
287
288
// Check argument can be useful for quickly checking the result of some simple expression
289
// that returns a boolean value.
290
func Check(isValid bool) Checker {
291 1
	return Checker{
292
		isValid:         isValid,
293
		code:            code.NotValid,
294
		messageTemplate: message.Templates[code.NotValid],
295
	}
296
}
297
298
// CheckProperty argument is an alias for Check that automatically adds property name to the current scope.
299
// It is useful to apply a simple checks on structs.
300
func CheckProperty(name string, isValid bool) Checker {
301 1
	return Checker{
302
		propertyName:    name,
303
		isValid:         isValid,
304
		code:            code.NotValid,
305
		messageTemplate: message.Templates[code.NotValid],
306
	}
307
}
308
309
// Language argument sets the current language for translation of a violation message.
310
func Language(tag language.Tag) Argument {
311 1
	return argumentFunc(func(arguments *Arguments) error {
312 1
		arguments.scope.language = tag
313
314 1
		return nil
315
	})
316
}
317
318
// NewArgument can be used to implement your own validation arguments for the specific types.
319
// See example for more details.
320
func NewArgument(options []Option, validate ValidateByConstraintFunc) Argument {
321 1
	return argumentFunc(func(arguments *Arguments) error {
322 1
		arguments.addValidator(newValidator(options, validate))
323
324 1
		return nil
325
	})
326
}
327
328
// Checker is an argument that can be useful for quickly checking the result of
329
// some simple expression that returns a boolean value.
330
type Checker struct {
331
	isIgnored         bool
332
	isValid           bool
333
	propertyName      string
334
	groups            []string
335
	code              string
336
	messageTemplate   string
337
	messageParameters TemplateParameterList
338
}
339
340
// When enables conditional validation of this constraint. If the expression evaluates to false,
341
// then the constraint will be ignored.
342
func (c Checker) When(condition bool) Checker {
343 1
	c.isIgnored = !condition
344 1
	return c
345
}
346
347
// WhenGroups enables conditional validation of the constraint by using the validation groups.
348
func (c Checker) WhenGroups(groups ...string) Checker {
349 1
	c.groups = groups
350 1
	return c
351
}
352
353
// Code overrides default code for produced violation.
354
func (c Checker) Code(code string) Checker {
355 1
	c.code = code
356 1
	return c
357
}
358
359
// Message sets the violation message template. You can set custom template parameters
360
// for injecting its values into the final message.
361
func (c Checker) Message(template string, parameters ...TemplateParameter) Checker {
362 1
	c.messageTemplate = template
363 1
	c.messageParameters = parameters
364 1
	return c
365
}
366
367
func (c Checker) set(arguments *Arguments) error {
368 1
	arguments.addValidator(c.validate)
369 1
	return nil
370
}
371
372
func (c Checker) validate(scope Scope) (*ViolationList, error) {
373 1
	if c.isValid || c.isIgnored || scope.IsIgnored(c.groups...) {
374 1
		return nil, nil
375
	}
376 1
	if c.propertyName != "" {
377 1
		scope = scope.AtProperty(c.propertyName)
378
	}
379
380 1
	violation := scope.BuildViolation(c.code, c.messageTemplate).
381
		SetParameters(c.messageParameters...).
382
		CreateViolation()
383
384 1
	return NewViolationList(violation), nil
385
}
386