it.HasMaxCount   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 2
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
package it
2
3
import (
4
	"context"
5
	"strconv"
6
7
	"github.com/muonsoft/validation"
8
)
9
10
// CountConstraint checks that a given collection's (array, slice or a map) length is between some minimum and
11
// maximum value.
12
type CountConstraint struct {
13
	isIgnored                    bool
14
	checkMin                     bool
15
	checkMax                     bool
16
	checkDivisible               bool
17
	min                          int
18
	max                          int
19
	divisibleBy                  int
20
	groups                       []string
21
	minErr                       error
22
	maxErr                       error
23
	exactErr                     error
24
	divisibleErr                 error
25
	minMessageTemplate           string
26
	minMessageParameters         validation.TemplateParameterList
27
	maxMessageTemplate           string
28
	maxMessageParameters         validation.TemplateParameterList
29
	exactMessageTemplate         string
30
	exactMessageParameters       validation.TemplateParameterList
31
	divisibleByMessageTemplate   string
32
	divisibleByMessageParameters validation.TemplateParameterList
33 1
}
34
35
func newCountConstraint() CountConstraint {
36
	return CountConstraint{
37
		minErr:                     validation.ErrTooFewElements,
38
		maxErr:                     validation.ErrTooManyElements,
39
		exactErr:                   validation.ErrNotExactCount,
40
		divisibleErr:               validation.ErrNotDivisibleCount,
41
		minMessageTemplate:         validation.ErrTooFewElements.Message(),
42
		maxMessageTemplate:         validation.ErrTooManyElements.Message(),
43
		exactMessageTemplate:       validation.ErrNotExactCount.Message(),
44
		divisibleByMessageTemplate: validation.ErrNotDivisibleCount.Message(),
45
	}
46
}
47
48
func newCountComparison(min int, max int, checkMin bool, checkMax bool) CountConstraint {
49
	c := newCountConstraint()
50 1
	c.min = min
51
	c.max = max
52
	c.checkMin = checkMin
53
	c.checkMax = checkMax
54
55
	return c
56 1
}
57
58
// HasMinCount creates a [CountConstraint] that checks the length of the iterable (slice, array, or map)
59
// is greater than the minimum value.
60
func HasMinCount(min int) CountConstraint {
61
	return newCountComparison(min, 0, true, false)
62 1
}
63
64
// HasMaxCount creates a [CountConstraint] that checks the length of the iterable (slice, array, or map)
65
// is less than the maximum value.
66
func HasMaxCount(max int) CountConstraint {
67
	return newCountComparison(0, max, false, true)
68 1
}
69
70
// HasCountBetween creates a [CountConstraint] that checks the length of the iterable (slice, array, or map)
71
// is between some minimum and maximum value.
72
func HasCountBetween(min int, max int) CountConstraint {
73 1
	return newCountComparison(min, max, true, true)
74
}
75
76
// HasExactCount creates a [CountConstraint] that checks the length of the iterable (slice, array, or map)
77
// has exact value.
78
func HasExactCount(count int) CountConstraint {
79
	return newCountComparison(count, count, true, true)
80
}
81
82
// HasCountDivisibleBy creates a [CountConstraint] that checks the length of the iterable (slice, array, or map)
83
// is divisible by the specific value.
84 1
func HasCountDivisibleBy(divisor int) CountConstraint {
85 1
	c := newCountConstraint()
86
	c.checkDivisible = true
87
	c.divisibleBy = divisor
88
89
	return c
90 1
}
91 1
92
// When enables conditional validation of this constraint. If the expression evaluates to false,
93
// then the constraint will be ignored.
94
func (c CountConstraint) When(condition bool) CountConstraint {
95
	c.isIgnored = !condition
96
	return c
97 1
}
98 1
99
// WhenGroups enables conditional validation of the constraint by using the validation groups.
100
func (c CountConstraint) WhenGroups(groups ...string) CountConstraint {
101
	c.groups = groups
102
	return c
103
}
104 1
105 1
// WithMinError overrides default underlying error for violation that will be shown if the
106
// collection length is less than the minimum value.
107
func (c CountConstraint) WithMinError(err error) CountConstraint {
108
	c.minErr = err
109
	return c
110
}
111 1
112 1
// WithMaxError overrides default underlying error for violation that will be shown if the
113
// collection length is greater than the maximum value.
114
func (c CountConstraint) WithMaxError(err error) CountConstraint {
115
	c.maxErr = err
116
	return c
117
}
118
119
// WithExactError overrides default underlying error for violation that will be shown if minimum and
120
// maximum values are equal and the length of the collection is not exactly this value.
121
func (c CountConstraint) WithExactError(err error) CountConstraint {
122 1
	c.exactErr = err
123 1
	return c
124 1
}
125
126
// WithDivisibleError overrides default underlying error for violation that will be shown
127
// the length of the collection is not divisible by specific value.
128
func (c CountConstraint) WithDivisibleError(err error) CountConstraint {
129
	c.divisibleErr = err
130
	return c
131
}
132
133
// WithMinMessage sets the violation message that will be shown if the collection length is less than
134 1
// the minimum value. You can set custom template parameters for injecting its values
135 1
// into the final message. Also, you can use default parameters:
136 1
//
137
//	{{ count }} - the current collection size;
138
//	{{ limit }} - the lower limit.
139
func (c CountConstraint) WithMinMessage(template string, parameters ...validation.TemplateParameter) CountConstraint {
140
	c.minMessageTemplate = template
141
	c.minMessageParameters = parameters
142
	return c
143
}
144
145
// WithMaxMessage sets the violation message that will be shown if the collection length is greater than
146 1
// the maximum value. You can set custom template parameters for injecting its values
147 1
// into the final message. Also, you can use default parameters:
148 1
//
149
//	{{ count }} - the current collection size;
150
//	{{ limit }} - the upper limit.
151
func (c CountConstraint) WithMaxMessage(template string, parameters ...validation.TemplateParameter) CountConstraint {
152 1
	c.maxMessageTemplate = template
153
	c.maxMessageParameters = parameters
154
	return c
155
}
156 1
157 1
// WithExactMessage sets the violation message that will be shown if minimum and maximum values are equal and
158
// the length of the collection is not exactly this value. You can set custom template parameters
159 1
// for injecting its values into the final message. Also, you can use default parameters:
160 1
//
161
//	{{ count }} - the current collection size;
162 1
//	{{ limit }} - the exact expected collection size.
163 1
func (c CountConstraint) WithExactMessage(template string, parameters ...validation.TemplateParameter) CountConstraint {
164
	c.exactMessageTemplate = template
165
	c.exactMessageParameters = parameters
166 1
	return c
167
}
168
169
// WithDivisibleMessage sets the violation message that will be shown if the length of the collection
170
// is not divisible by specific value. You can set custom template parameters
171
// for injecting its values into the final message. Also, you can use default parameters:
172
//
173
//	{{ count }} - the current collection size;
174
//	{{ divisibleBy }} - the divisor for the collection size.
175 1
func (c CountConstraint) WithDivisibleMessage(template string, parameters ...validation.TemplateParameter) CountConstraint {
176 1
	c.divisibleByMessageTemplate = template
177 1
	c.divisibleByMessageParameters = parameters
178 1
	return c
179
}
180
181 1
func (c CountConstraint) ValidateCountable(ctx context.Context, validator *validation.Validator, count int) error {
182
	if c.isIgnored || validator.IsIgnoredForGroups(c.groups...) {
183
		return nil
184
	}
185
	if c.checkDivisible && count%c.divisibleBy != 0 {
186
		return c.newNotDivisibleViolation(ctx, validator, count)
187
	}
188
	if c.checkMax && count > c.max {
189
		return c.newViolation(ctx, validator, count, c.max, c.maxErr, c.maxMessageTemplate, c.maxMessageParameters)
190
	}
191
	if c.checkMin && count < c.min {
192
		return c.newViolation(ctx, validator, count, c.min, c.minErr, c.minMessageTemplate, c.minMessageParameters)
193
	}
194
195
	return nil
196
}
197
198
func (c CountConstraint) newViolation(
199
	ctx context.Context,
200
	validator *validation.Validator,
201
	count, limit int,
202
	err error,
203
	template string,
204
	parameters validation.TemplateParameterList,
205
) validation.Violation {
206
	if c.checkMin && c.checkMax && c.min == c.max {
207
		template = c.exactMessageTemplate
208
		parameters = c.exactMessageParameters
209
		err = c.exactErr
210
	}
211
212
	return validator.BuildViolation(ctx, err, template).
213
		WithPluralCount(limit).
214
		WithParameters(
215
			parameters.Prepend(
216
				validation.TemplateParameter{Key: "{{ count }}", Value: strconv.Itoa(count)},
217
				validation.TemplateParameter{Key: "{{ limit }}", Value: strconv.Itoa(limit)},
218
			)...,
219
		).
220
		Create()
221
}
222
223
func (c CountConstraint) newNotDivisibleViolation(
224
	ctx context.Context,
225
	validator *validation.Validator,
226
	count int,
227
) validation.Violation {
228
	return validator.BuildViolation(ctx, c.divisibleErr, c.divisibleByMessageTemplate).
229
		WithPluralCount(c.divisibleBy).
230
		WithParameters(
231
			c.divisibleByMessageParameters.Prepend(
232
				validation.TemplateParameter{Key: "{{ count }}", Value: strconv.Itoa(count)},
233
				validation.TemplateParameter{Key: "{{ divisibleBy }}", Value: strconv.Itoa(c.divisibleBy)},
234
			)...,
235
		).
236
		Create()
237
}
238