Passed
Push — main ( aa8c07...4607a7 )
by Igor
02:58 queued 01:09
created

it.HasUniqueValues   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 0
dl 0
loc 4
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
package it
2
3
import (
4
	"strconv"
5
	"time"
6
7
	"github.com/muonsoft/validation"
8
	"github.com/muonsoft/validation/code"
9
	"github.com/muonsoft/validation/generic"
10
	"github.com/muonsoft/validation/is"
11
	"github.com/muonsoft/validation/message"
12
)
13
14
// NumberComparisonConstraint is used for various numeric comparisons between integer and float values.
15
// Values are compared as integers if the compared and specified values are integers.
16
// Otherwise, numbers are always compared as floating point numbers.
17
type NumberComparisonConstraint struct {
18
	isIgnored         bool
19
	code              string
20
	messageTemplate   string
21
	messageParameters validation.TemplateParameterList
22
	comparedValue     string
23
	isValid           func(value generic.Number) bool
24
}
25
26
// IsEqualToInteger checks that the number (integer or float) is equal to the specified integer value.
27
// Values are compared as integers if the compared and specified values are integers.
28
// Otherwise, numbers are always compared as floating point numbers.
29
func IsEqualToInteger(value int64) NumberComparisonConstraint {
30 1
	v := generic.NewNumberFromInt(value)
31
32 1
	return NumberComparisonConstraint{
33
		code:            code.Equal,
34
		messageTemplate: message.Equal,
35
		comparedValue:   v.String(),
36
		isValid: func(n generic.Number) bool {
37 1
			return n.IsEqualTo(v)
38
		},
39
	}
40
}
41
42
// IsEqualToFloat checks that the number (integer or float) is equal to the specified float value.
43
// Values are compared as integers if the compared and specified values are integers.
44
// Otherwise, numbers are always compared as floating point numbers.
45
func IsEqualToFloat(value float64) NumberComparisonConstraint {
46 1
	v := generic.NewNumberFromFloat(value)
47
48 1
	return NumberComparisonConstraint{
49
		code:            code.Equal,
50
		messageTemplate: message.Equal,
51
		comparedValue:   v.String(),
52
		isValid: func(n generic.Number) bool {
53 1
			return n.IsEqualTo(v)
54
		},
55
	}
56
}
57
58
// IsNotEqualToInteger checks that the number (integer or float) is not equal to the specified integer value.
59
// Values are compared as integers if the compared and specified values are integers.
60
// Otherwise, numbers are always compared as floating point numbers.
61
func IsNotEqualToInteger(value int64) NumberComparisonConstraint {
62 1
	v := generic.NewNumberFromInt(value)
63
64 1
	return NumberComparisonConstraint{
65
		code:            code.NotEqual,
66
		messageTemplate: message.NotEqual,
67
		comparedValue:   v.String(),
68
		isValid: func(n generic.Number) bool {
69 1
			return !n.IsEqualTo(v)
70
		},
71
	}
72
}
73
74
// IsNotEqualToFloat checks that the number (integer or float) is not equal to the specified float value.
75
// Values are compared as integers if the compared and specified values are integers.
76
// Otherwise, numbers are always compared as floating point numbers.
77
func IsNotEqualToFloat(value float64) NumberComparisonConstraint {
78 1
	v := generic.NewNumberFromFloat(value)
79
80 1
	return NumberComparisonConstraint{
81
		code:            code.NotEqual,
82
		messageTemplate: message.NotEqual,
83
		comparedValue:   v.String(),
84
		isValid: func(n generic.Number) bool {
85 1
			return !n.IsEqualTo(v)
86
		},
87
	}
88
}
89
90
// IsLessThanInteger checks that the number (integer or float) is less than the specified integer value.
91
// Values are compared as integers if the compared and specified values are integers.
92
// Otherwise, numbers are always compared as floating point numbers.
93
func IsLessThanInteger(value int64) NumberComparisonConstraint {
94 1
	v := generic.NewNumberFromInt(value)
95
96 1
	return NumberComparisonConstraint{
97
		code:            code.TooHigh,
98
		messageTemplate: message.TooHigh,
99
		comparedValue:   v.String(),
100
		isValid: func(n generic.Number) bool {
101 1
			return n.IsLessThan(v)
102
		},
103
	}
104
}
105
106
// IsLessThanFloat checks that the number (integer or float) is less than the specified float value.
107
// Values are compared as integers if the compared and specified values are integers.
108
// Otherwise, numbers are always compared as floating point numbers.
109
func IsLessThanFloat(value float64) NumberComparisonConstraint {
110 1
	v := generic.NewNumberFromFloat(value)
111
112 1
	return NumberComparisonConstraint{
113
		code:            code.TooHigh,
114
		messageTemplate: message.TooHigh,
115
		comparedValue:   v.String(),
116
		isValid: func(n generic.Number) bool {
117 1
			return n.IsLessThan(v)
118
		},
119
	}
120
}
121
122
// IsLessThanOrEqualInteger checks that the number (integer or float) is less than or
123
// equal to the specified integer value. Values are compared as integers if the compared
124
// and specified values are integers. Otherwise, numbers are always compared as floating point numbers.
125
func IsLessThanOrEqualInteger(value int64) NumberComparisonConstraint {
126 1
	v := generic.NewNumberFromInt(value)
127
128 1
	return NumberComparisonConstraint{
129
		code:            code.TooHighOrEqual,
130
		messageTemplate: message.TooHighOrEqual,
131
		comparedValue:   v.String(),
132
		isValid: func(n generic.Number) bool {
133 1
			return n.IsLessThan(v) || n.IsEqualTo(v)
134
		},
135
	}
136
}
137
138
// IsLessThanOrEqualFloat checks that the number (integer or float) is less than or
139
// equal to the specified float value. Values are compared as integers if the compared
140
// and specified values are integers. Otherwise, numbers are always compared as floating point numbers.
141
func IsLessThanOrEqualFloat(value float64) NumberComparisonConstraint {
142 1
	v := generic.NewNumberFromFloat(value)
143
144 1
	return NumberComparisonConstraint{
145
		code:            code.TooHighOrEqual,
146
		messageTemplate: message.TooHighOrEqual,
147
		comparedValue:   v.String(),
148
		isValid: func(n generic.Number) bool {
149 1
			return n.IsLessThan(v) || n.IsEqualTo(v)
150
		},
151
	}
152
}
153
154
// IsGreaterThanInteger checks that the number (integer or float) is greater than the specified integer value.
155
// Values are compared as integers if the compared and specified values are integers.
156
// Otherwise, numbers are always compared as floating point numbers.
157
func IsGreaterThanInteger(value int64) NumberComparisonConstraint {
158 1
	v := generic.NewNumberFromInt(value)
159
160 1
	return NumberComparisonConstraint{
161
		code:            code.TooLow,
162
		messageTemplate: message.TooLow,
163
		comparedValue:   v.String(),
164
		isValid: func(n generic.Number) bool {
165 1
			return n.IsGreaterThan(v)
166
		},
167
	}
168
}
169
170
// IsGreaterThanFloat checks that the number (integer or float) is greater than the specified float value.
171
// Values are compared as integers if the compared and specified values are integers.
172
// Otherwise, numbers are always compared as floating point numbers.
173
func IsGreaterThanFloat(value float64) NumberComparisonConstraint {
174 1
	v := generic.NewNumberFromFloat(value)
175
176 1
	return NumberComparisonConstraint{
177
		code:            code.TooLow,
178
		messageTemplate: message.TooLow,
179
		comparedValue:   v.String(),
180
		isValid: func(n generic.Number) bool {
181 1
			return n.IsGreaterThan(v)
182
		},
183
	}
184
}
185
186
// IsGreaterThanOrEqualInteger checks that the number (integer or float) is greater than or
187
// equal to the specified integer value. Values are compared as integers if the compared
188
// and specified values are integers. Otherwise, numbers are always compared as floating point numbers.
189
func IsGreaterThanOrEqualInteger(value int64) NumberComparisonConstraint {
190 1
	v := generic.NewNumberFromInt(value)
191
192 1
	return NumberComparisonConstraint{
193
		code:            code.TooLowOrEqual,
194
		messageTemplate: message.TooLowOrEqual,
195
		comparedValue:   v.String(),
196
		isValid: func(n generic.Number) bool {
197 1
			return n.IsGreaterThan(v) || n.IsEqualTo(v)
198
		},
199
	}
200
}
201
202
// IsGreaterThanOrEqualFloat checks that the number (integer or float) is greater than or
203
// equal to the specified float value. Values are compared as integers if the compared
204
// and specified values are integers. Otherwise, numbers are always compared as floating point numbers.
205
func IsGreaterThanOrEqualFloat(value float64) NumberComparisonConstraint {
206 1
	v := generic.NewNumberFromFloat(value)
207
208 1
	return NumberComparisonConstraint{
209
		code:            code.TooLowOrEqual,
210
		messageTemplate: message.TooLowOrEqual,
211
		comparedValue:   v.String(),
212
		isValid: func(n generic.Number) bool {
213 1
			return n.IsGreaterThan(v) || n.IsEqualTo(v)
214
		},
215
	}
216
}
217
218
// IsPositive checks that the value is a positive number (integer or float). Zero is neither
219
// positive nor negative. If you want to allow zero use IsPositiveOrZero comparison.
220
func IsPositive() NumberComparisonConstraint {
221 1
	v := generic.NewNumberFromInt(0)
222
223 1
	return NumberComparisonConstraint{
224
		code:            code.NotPositive,
225
		messageTemplate: message.NotPositive,
226
		comparedValue:   v.String(),
227
		isValid: func(n generic.Number) bool {
228 1
			return n.IsGreaterThan(v)
229
		},
230
	}
231
}
232
233
// IsPositiveOrZero checks that the value is a positive number (integer or float) or equal to zero.
234
// If you don't want to allow zero as a valid value, use IsPositive comparison.
235
func IsPositiveOrZero() NumberComparisonConstraint {
236 1
	v := generic.NewNumberFromInt(0)
237
238 1
	return NumberComparisonConstraint{
239
		code:            code.NotPositiveOrZero,
240
		messageTemplate: message.NotPositiveOrZero,
241
		comparedValue:   v.String(),
242
		isValid: func(n generic.Number) bool {
243 1
			return n.IsGreaterThan(v) || n.IsEqualTo(v)
244
		},
245
	}
246
}
247
248
// IsNegative checks that the value is a negative number (integer or float). Zero is neither
249
// positive nor negative. If you want to allow zero use IsNegativeOrZero comparison.
250
func IsNegative() NumberComparisonConstraint {
251 1
	v := generic.NewNumberFromInt(0)
252
253 1
	return NumberComparisonConstraint{
254
		code:            code.NotNegative,
255
		messageTemplate: message.NotNegative,
256
		comparedValue:   v.String(),
257
		isValid: func(n generic.Number) bool {
258 1
			return n.IsLessThan(v)
259
		},
260
	}
261
}
262
263
// IsNegativeOrZero checks that the value is a negative number (integer or float) or equal to zero.
264
// If you don't want to allow zero as a valid value, use IsNegative comparison.
265
func IsNegativeOrZero() NumberComparisonConstraint {
266 1
	v := generic.NewNumberFromInt(0)
267
268 1
	return NumberComparisonConstraint{
269
		code:            code.NotNegativeOrZero,
270
		messageTemplate: message.NotNegativeOrZero,
271
		comparedValue:   v.String(),
272
		isValid: func(n generic.Number) bool {
273 1
			return n.IsLessThan(v) || n.IsEqualTo(v)
274
		},
275
	}
276
}
277
278
// SetUp always returns no error.
279
func (c NumberComparisonConstraint) SetUp() error {
280 1
	return nil
281
}
282
283
// Name is the constraint name.
284
func (c NumberComparisonConstraint) Name() string {
285
	return "NumberComparisonConstraint"
286
}
287
288
// Code overrides default code for produced violation.
289
func (c NumberComparisonConstraint) Code(code string) NumberComparisonConstraint {
290 1
	c.code = code
291 1
	return c
292
}
293
294
// Message sets the violation message template. You can set custom template parameters
295
// for injecting its values into the final message. Also, you can use default parameters:
296
//
297
//  {{ comparedValue }} - the expected value;
298
//  {{ value }} - the current (invalid) value.
299
func (c NumberComparisonConstraint) Message(
300
	template string,
301
	parameters ...validation.TemplateParameter,
302
) NumberComparisonConstraint {
303 1
	c.messageTemplate = template
304 1
	c.messageParameters = parameters
305 1
	return c
306
}
307
308
// When enables conditional validation of this constraint. If the expression evaluates to false,
309
// then the constraint will be ignored.
310
func (c NumberComparisonConstraint) When(condition bool) NumberComparisonConstraint {
311 1
	c.isIgnored = !condition
312 1
	return c
313
}
314
315
func (c NumberComparisonConstraint) ValidateNumber(value generic.Number, scope validation.Scope) error {
316 1
	if c.isIgnored || value.IsNil() || c.isValid(value) {
317 1
		return nil
318
	}
319
320 1
	return scope.BuildViolation(c.code, c.messageTemplate).
321
		SetParameters(
322
			c.messageParameters.Prepend(
323
				validation.TemplateParameter{Key: "{{ comparedValue }}", Value: c.comparedValue},
324
				validation.TemplateParameter{Key: "{{ value }}", Value: value.String()},
325
			)...,
326
		).
327
		CreateViolation()
328
}
329
330
// RangeConstraint is used to check that a given number value is between some minimum and maximum.
331
// Values are compared as integers if the compared and specified values are integers.
332
// Otherwise, numbers are always compared as floating point numbers.
333
type RangeConstraint struct {
334
	isIgnored         bool
335
	code              string
336
	messageTemplate   string
337
	messageParameters validation.TemplateParameterList
338
	min               generic.Number
339
	max               generic.Number
340
}
341
342
// IsBetweenIntegers checks that the number (integer or float) is between specified minimum and
343
// maximum integer values. Values are compared as integers if the compared and specified
344
// values are integers. Otherwise, numbers are always compared as floating point numbers.
345
func IsBetweenIntegers(min, max int64) RangeConstraint {
346 1
	return RangeConstraint{
347
		min:             generic.NewNumberFromInt(min),
348
		max:             generic.NewNumberFromInt(max),
349
		code:            code.NotInRange,
350
		messageTemplate: message.NotInRange,
351
	}
352
}
353
354
// IsBetweenFloats checks that the number (integer or float) is between specified minimum and
355
// maximum float values. Values are compared as integers if the compared and specified
356
// values are integers. Otherwise, numbers are always compared as floating point numbers.
357
func IsBetweenFloats(min, max float64) RangeConstraint {
358 1
	return RangeConstraint{
359
		min:             generic.NewNumberFromFloat(min),
360
		max:             generic.NewNumberFromFloat(max),
361
		code:            code.NotInRange,
362
		messageTemplate: message.NotInRange,
363
	}
364
}
365
366
// SetUp returns an error if min is greater than or equal to max.
367
func (c RangeConstraint) SetUp() error {
368 1
	if c.min.IsGreaterThan(c.max) || c.min.IsEqualTo(c.max) {
369 1
		return errInvalidRange
370
	}
371
372 1
	return nil
373
}
374
375
// Name is the constraint name.
376
func (c RangeConstraint) Name() string {
377 1
	return "RangeConstraint"
378
}
379
380
// Code overrides default code for produced violation.
381
func (c RangeConstraint) Code(code string) RangeConstraint {
382 1
	c.code = code
383 1
	return c
384
}
385
386
// Message sets the violation message template. You can set custom template parameters
387
// for injecting its values into the final message. Also, you can use default parameters:
388
//
389
//  {{ max }} - the upper limit;
390
//  {{ min }} - the lower limit;
391
//  {{ value }} - the current (invalid) value.
392
func (c RangeConstraint) Message(template string, parameters ...validation.TemplateParameter) RangeConstraint {
393 1
	c.messageTemplate = template
394 1
	c.messageParameters = parameters
395 1
	return c
396
}
397
398
// When enables conditional validation of this constraint. If the expression evaluates to false,
399
// then the constraint will be ignored.
400
func (c RangeConstraint) When(condition bool) RangeConstraint {
401 1
	c.isIgnored = !condition
402 1
	return c
403
}
404
405
func (c RangeConstraint) ValidateNumber(value generic.Number, scope validation.Scope) error {
406 1
	if c.isIgnored {
407 1
		return nil
408
	}
409 1
	if value.IsLessThan(c.min) || value.IsGreaterThan(c.max) {
410 1
		return c.newViolation(value, scope)
411
	}
412
413 1
	return nil
414
}
415
416
func (c RangeConstraint) newViolation(value generic.Number, scope validation.Scope) error {
417 1
	return scope.BuildViolation(c.code, c.messageTemplate).
418
		SetParameters(
419
			c.messageParameters.Prepend(
420
				validation.TemplateParameter{Key: "{{ min }}", Value: c.min.String()},
421
				validation.TemplateParameter{Key: "{{ max }}", Value: c.max.String()},
422
				validation.TemplateParameter{Key: "{{ value }}", Value: value.String()},
423
			)...,
424
		).
425
		CreateViolation()
426
}
427
428
// StringComparisonConstraint is used to compare strings.
429
type StringComparisonConstraint struct {
430
	isIgnored         bool
431
	code              string
432
	messageTemplate   string
433
	messageParameters validation.TemplateParameterList
434
	comparedValue     string
435
	isValid           func(value string) bool
436
}
437
438
// IsEqualToString checks that the string value is equal to the specified string value.
439
func IsEqualToString(value string) StringComparisonConstraint {
440 1
	return StringComparisonConstraint{
441
		code:            code.Equal,
442
		messageTemplate: message.Equal,
443
		comparedValue:   value,
444
		isValid: func(actualValue string) bool {
445 1
			return value == actualValue
446
		},
447
	}
448
}
449
450
// IsNotEqualToString checks that the string value is not equal to the specified string value.
451
func IsNotEqualToString(value string) StringComparisonConstraint {
452 1
	return StringComparisonConstraint{
453
		code:            code.NotEqual,
454
		messageTemplate: message.NotEqual,
455
		comparedValue:   value,
456
		isValid: func(actualValue string) bool {
457 1
			return value != actualValue
458
		},
459
	}
460
}
461
462
// SetUp always returns no error.
463
func (c StringComparisonConstraint) SetUp() error {
464 1
	return nil
465
}
466
467
// Name is the constraint name.
468
func (c StringComparisonConstraint) Name() string {
469
	return "StringComparisonConstraint"
470
}
471
472
// Code overrides default code for produced violation.
473
func (c StringComparisonConstraint) Code(code string) StringComparisonConstraint {
474 1
	c.code = code
475 1
	return c
476
}
477
478
// Message sets the violation message template. You can set custom template parameters
479
// for injecting its values into the final message. Also, you can use default parameters:
480
//
481
//  {{ comparedValue }} - the expected value;
482
//  {{ value }} - the current (invalid) value.
483
//
484
// All string values are quoted strings.
485
func (c StringComparisonConstraint) Message(
486
	template string,
487
	parameters ...validation.TemplateParameter,
488
) StringComparisonConstraint {
489 1
	c.messageTemplate = template
490 1
	c.messageParameters = parameters
491 1
	return c
492
}
493
494
// When enables conditional validation of this constraint. If the expression evaluates to false,
495
// then the constraint will be ignored.
496
func (c StringComparisonConstraint) When(condition bool) StringComparisonConstraint {
497 1
	c.isIgnored = !condition
498 1
	return c
499
}
500
501
func (c StringComparisonConstraint) ValidateString(value *string, scope validation.Scope) error {
502 1
	if c.isIgnored || value == nil || c.isValid(*value) {
503 1
		return nil
504
	}
505
506 1
	return scope.BuildViolation(c.code, c.messageTemplate).
507
		SetParameters(
508
			c.messageParameters.Prepend(
509
				validation.TemplateParameter{Key: "{{ comparedValue }}", Value: strconv.Quote(c.comparedValue)},
510
				validation.TemplateParameter{Key: "{{ value }}", Value: strconv.Quote(*value)},
511
			)...,
512
		).
513
		CreateViolation()
514
}
515
516
// TimeComparisonConstraint is used to compare time values.
517
type TimeComparisonConstraint struct {
518
	isIgnored         bool
519
	code              string
520
	messageTemplate   string
521
	messageParameters validation.TemplateParameterList
522
	comparedValue     time.Time
523
	layout            string
524
	isValid           func(value time.Time) bool
525
}
526
527
// IsEarlierThan checks that the given time is earlier than the specified value.
528
func IsEarlierThan(value time.Time) TimeComparisonConstraint {
529 1
	return TimeComparisonConstraint{
530
		code:            code.TooLate,
531
		messageTemplate: message.TooLate,
532
		comparedValue:   value,
533
		layout:          time.RFC3339,
534
		isValid: func(actualValue time.Time) bool {
535 1
			return actualValue.Before(value)
536
		},
537
	}
538
}
539
540
// IsEarlierThanOrEqual checks that the given time is earlier or equal to the specified value.
541
func IsEarlierThanOrEqual(value time.Time) TimeComparisonConstraint {
542 1
	return TimeComparisonConstraint{
543
		code:            code.TooLateOrEqual,
544
		messageTemplate: message.TooLateOrEqual,
545
		comparedValue:   value,
546
		layout:          time.RFC3339,
547
		isValid: func(actualValue time.Time) bool {
548 1
			return actualValue.Before(value) || actualValue.Equal(value)
549
		},
550
	}
551
}
552
553
// IsLaterThan checks that the given time is later than the specified value.
554
func IsLaterThan(value time.Time) TimeComparisonConstraint {
555 1
	return TimeComparisonConstraint{
556
		code:            code.TooEarly,
557
		messageTemplate: message.TooEarly,
558
		comparedValue:   value,
559
		layout:          time.RFC3339,
560
		isValid: func(actualValue time.Time) bool {
561 1
			return actualValue.After(value)
562
		},
563
	}
564
}
565
566
// IsLaterThanOrEqual checks that the given time is later or equal to the specified value.
567
func IsLaterThanOrEqual(value time.Time) TimeComparisonConstraint {
568 1
	return TimeComparisonConstraint{
569
		code:            code.TooEarlyOrEqual,
570
		messageTemplate: message.TooEarlyOrEqual,
571
		comparedValue:   value,
572
		layout:          time.RFC3339,
573
		isValid: func(actualValue time.Time) bool {
574 1
			return actualValue.After(value) || actualValue.Equal(value)
575
		},
576
	}
577
}
578
579
// SetUp always returns no error.
580
func (c TimeComparisonConstraint) SetUp() error {
581 1
	return nil
582
}
583
584
// Name is the constraint name.
585
func (c TimeComparisonConstraint) Name() string {
586
	return "TimeComparisonConstraint"
587
}
588
589
// Code overrides default code for produced violation.
590
func (c TimeComparisonConstraint) Code(code string) TimeComparisonConstraint {
591 1
	c.code = code
592 1
	return c
593
}
594
595
// Message sets the violation message template. You can set custom template parameters
596
// for injecting its values into the final message. Also, you can use default parameters:
597
//
598
//  {{ comparedValue }} - the expected value;
599
//  {{ value }} - the current (invalid) value.
600
//
601
// All values are formatted by the layout that can be defined by the Layout method.
602
// Default layout is time.RFC3339.
603
func (c TimeComparisonConstraint) Message(
604
	template string,
605
	parameters ...validation.TemplateParameter,
606
) TimeComparisonConstraint {
607 1
	c.messageTemplate = template
608 1
	c.messageParameters = parameters
609 1
	return c
610
}
611
612
// Layout can be used to set the layout that is used to format time values.
613
func (c TimeComparisonConstraint) Layout(layout string) TimeComparisonConstraint {
614 1
	c.layout = layout
615 1
	return c
616
}
617
618
// When enables conditional validation of this constraint. If the expression evaluates to false,
619
// then the constraint will be ignored.
620
func (c TimeComparisonConstraint) When(condition bool) TimeComparisonConstraint {
621 1
	c.isIgnored = !condition
622 1
	return c
623
}
624
625
func (c TimeComparisonConstraint) ValidateTime(value *time.Time, scope validation.Scope) error {
626 1
	if c.isIgnored || value == nil || c.isValid(*value) {
627 1
		return nil
628
	}
629
630 1
	return scope.BuildViolation(c.code, c.messageTemplate).
631
		SetParameters(
632
			c.messageParameters.Prepend(
633
				validation.TemplateParameter{Key: "{{ comparedValue }}", Value: c.comparedValue.Format(c.layout)},
634
				validation.TemplateParameter{Key: "{{ value }}", Value: value.Format(c.layout)},
635
			)...,
636
		).
637
		CreateViolation()
638
}
639
640
// TimeRangeConstraint is used to check that a given time value is between some minimum and maximum.
641
type TimeRangeConstraint struct {
642
	isIgnored         bool
643
	code              string
644
	messageTemplate   string
645
	messageParameters validation.TemplateParameterList
646
	layout            string
647
	min               time.Time
648
	max               time.Time
649
}
650
651
// IsBetweenTime checks that the time is between specified minimum and maximum time values.
652
func IsBetweenTime(min, max time.Time) TimeRangeConstraint {
653 1
	return TimeRangeConstraint{
654
		code:            code.NotInRange,
655
		messageTemplate: message.NotInRange,
656
		layout:          time.RFC3339,
657
		min:             min,
658
		max:             max,
659
	}
660
}
661
662
// SetUp returns an error if min is greater than or equal to max.
663
func (c TimeRangeConstraint) SetUp() error {
664 1
	if c.min.After(c.max) || c.min.Equal(c.max) {
665 1
		return errInvalidRange
666
	}
667
668 1
	return nil
669
}
670
671
// Name is the constraint name.
672
func (c TimeRangeConstraint) Name() string {
673 1
	return "TimeRangeConstraint"
674
}
675
676
// Code overrides default code for produced violation.
677
func (c TimeRangeConstraint) Code(code string) TimeRangeConstraint {
678 1
	c.code = code
679 1
	return c
680
}
681
682
// Message sets the violation message template. You can set custom template parameters
683
// for injecting its values into the final message. Also, you can use default parameters:
684
//
685
//  {{ max }} - the upper limit;
686
//  {{ min }} - the lower limit;
687
//  {{ value }} - the current (invalid) value.
688
//
689
// All values are formatted by the layout that can be defined by the Layout method.
690
// Default layout is time.RFC3339.
691
func (c TimeRangeConstraint) Message(template string, parameters ...validation.TemplateParameter) TimeRangeConstraint {
692 1
	c.messageTemplate = template
693 1
	c.messageParameters = parameters
694 1
	return c
695
}
696
697
// When enables conditional validation of this constraint. If the expression evaluates to false,
698
// then the constraint will be ignored.
699
func (c TimeRangeConstraint) When(condition bool) TimeRangeConstraint {
700 1
	c.isIgnored = !condition
701 1
	return c
702
}
703
704
// Layout can be used to set the layout that is used to format time values.
705
func (c TimeRangeConstraint) Layout(layout string) TimeRangeConstraint {
706 1
	c.layout = layout
707 1
	return c
708
}
709
710
func (c TimeRangeConstraint) ValidateTime(value *time.Time, scope validation.Scope) error {
711 1
	if c.isIgnored || value == nil {
712 1
		return nil
713
	}
714 1
	if value.Before(c.min) || value.After(c.max) {
715 1
		return c.newViolation(value, scope)
716
	}
717
718 1
	return nil
719
}
720
721
func (c TimeRangeConstraint) newViolation(value *time.Time, scope validation.Scope) validation.Violation {
722 1
	return scope.BuildViolation(c.code, c.messageTemplate).
723
		SetParameters(
724
			c.messageParameters.Prepend(
725
				validation.TemplateParameter{Key: "{{ min }}", Value: c.min.Format(c.layout)},
726
				validation.TemplateParameter{Key: "{{ max }}", Value: c.max.Format(c.layout)},
727
				validation.TemplateParameter{Key: "{{ value }}", Value: value.Format(c.layout)},
728
			)...,
729
		).
730
		CreateViolation()
731
}
732
733
// UniqueConstraint is used to check that all elements of the given collection are unique.
734
type UniqueConstraint struct {
735
	isIgnored         bool
736
	code              string
737
	messageTemplate   string
738
	messageParameters validation.TemplateParameterList
739
}
740
741
// HasUniqueValues checks that all elements of the given collection are unique
742
// (none of them is present more than once).
743
func HasUniqueValues() UniqueConstraint {
744 1
	return UniqueConstraint{
745
		code:            code.NotUnique,
746
		messageTemplate: message.NotUnique,
747
	}
748
}
749
750
// SetUp always returns no error.
751
func (c UniqueConstraint) SetUp() error {
752 1
	return nil
753
}
754
755
// Name is the constraint name.
756
func (c UniqueConstraint) Name() string {
757
	return "UniqueConstraint"
758
}
759
760
// Code overrides default code for produced violation.
761
func (c UniqueConstraint) Code(code string) UniqueConstraint {
762 1
	c.code = code
763 1
	return c
764
}
765
766
// Message sets the violation message template. You can set custom template parameters
767
// for injecting its values into the final message.
768
func (c UniqueConstraint) Message(template string, parameters ...validation.TemplateParameter) UniqueConstraint {
769 1
	c.messageTemplate = template
770 1
	c.messageParameters = parameters
771 1
	return c
772
}
773
774
// When enables conditional validation of this constraint. If the expression evaluates to false,
775
// then the constraint will be ignored.
776
func (c UniqueConstraint) When(condition bool) UniqueConstraint {
777 1
	c.isIgnored = !condition
778 1
	return c
779
}
780
781
func (c UniqueConstraint) ValidateStrings(values []string, scope validation.Scope) error {
782 1
	if c.isIgnored || is.UniqueStrings(values) {
783 1
		return nil
784
	}
785
786 1
	return scope.BuildViolation(c.code, c.messageTemplate).
787
		SetParameters(c.messageParameters...).
788
		CreateViolation()
789
}
790