it.HasExactLength   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
	"regexp"
6
	"strconv"
7
	"unicode/utf8"
8
9
	"github.com/muonsoft/validation"
10
	"github.com/muonsoft/validation/is"
11
)
12
13
// LengthConstraint checks that a given string length is between some minimum and maximum value.
14
// If you want to check the length of the array, slice or a map use [CountConstraint].
15
type LengthConstraint struct {
16
	isIgnored              bool
17
	checkMin               bool
18
	checkMax               bool
19
	min                    int
20
	max                    int
21
	groups                 []string
22
	minErr                 error
23
	maxErr                 error
24
	exactErr               error
25
	minMessageTemplate     string
26
	minMessageParameters   validation.TemplateParameterList
27
	maxMessageTemplate     string
28
	maxMessageParameters   validation.TemplateParameterList
29
	exactMessageTemplate   string
30
	exactMessageParameters validation.TemplateParameterList
31
}
32
33
func newLengthConstraint(min int, max int, checkMin bool, checkMax bool) LengthConstraint {
34
	return LengthConstraint{
35 1
		min:                  min,
36
		max:                  max,
37
		checkMin:             checkMin,
38
		checkMax:             checkMax,
39
		minErr:               validation.ErrTooShort,
40
		maxErr:               validation.ErrTooLong,
41
		exactErr:             validation.ErrNotExactLength,
42
		minMessageTemplate:   validation.ErrTooShort.Message(),
43
		maxMessageTemplate:   validation.ErrTooLong.Message(),
44
		exactMessageTemplate: validation.ErrNotExactLength.Message(),
45
	}
46
}
47
48
// HasMinLength creates a [LengthConstraint] that checks the length of the string
49
// is greater than the minimum value.
50
func HasMinLength(min int) LengthConstraint {
51
	return newLengthConstraint(min, 0, true, false)
52 1
}
53
54
// HasMaxLength creates a [LengthConstraint] that checks the length of the string
55
// is less than the maximum value.
56
func HasMaxLength(max int) LengthConstraint {
57
	return newLengthConstraint(0, max, false, true)
58 1
}
59
60
// HasLengthBetween creates a [LengthConstraint] that checks the length of the string
61
// is between some minimum and maximum value.
62
func HasLengthBetween(min int, max int) LengthConstraint {
63
	return newLengthConstraint(min, max, true, true)
64 1
}
65
66
// HasExactLength creates a [LengthConstraint] that checks the length of the string
67
// has exact value.
68
func HasExactLength(count int) LengthConstraint {
69
	return newLengthConstraint(count, count, true, true)
70 1
}
71
72
// When enables conditional validation of this constraint. If the expression evaluates to false,
73
// then the constraint will be ignored.
74
func (c LengthConstraint) When(condition bool) LengthConstraint {
75 1
	c.isIgnored = !condition
76
	return c
77
}
78
79
// WhenGroups enables conditional validation of the constraint by using the validation groups.
80
func (c LengthConstraint) WhenGroups(groups ...string) LengthConstraint {
81
	c.groups = groups
82
	return c
83
}
84
85
// WithMinError overrides default underlying error for violation that will be shown if the string length
86 1
// is less than the minimum value.
87 1
func (c LengthConstraint) WithMinError(err error) LengthConstraint {
88
	c.minErr = err
89
	return c
90
}
91
92 1
// WithMaxError overrides default underlying error for violation that will be shown if the string length
93 1
// is greater than the maximum value.
94
func (c LengthConstraint) WithMaxError(err error) LengthConstraint {
95
	c.maxErr = err
96
	return c
97
}
98
99 1
// WithExactError overrides default underlying error for violation that will be shown if minimum and maximum values
100 1
// are equal and the length of the string is not exactly this value.
101
func (c LengthConstraint) WithExactError(err error) LengthConstraint {
102
	c.exactErr = err
103
	return c
104
}
105
106 1
// WithMinMessage sets the violation message that will be shown if the string length is less than
107 1
// the minimum value. You can set custom template parameters for injecting its values
108
// into the final message. Also, you can use default parameters:
109
//
110
//	{{ length }} - the current string length;
111
//	{{ limit }} - the lower limit;
112
//	{{ value }} - the current (invalid) value.
113 1
func (c LengthConstraint) WithMinMessage(template string, parameters ...validation.TemplateParameter) LengthConstraint {
114 1
	c.minMessageTemplate = template
115
	c.minMessageParameters = parameters
116
	return c
117
}
118
119
// WithMaxMessage sets the violation message that will be shown if the string length is greater than
120
// the maximum value. You can set custom template parameters for injecting its values
121
// into the final message. Also, you can use default parameters:
122
//
123
//	{{ length }} - the current string length;
124
//	{{ limit }} - the lower limit;
125 1
//	{{ value }} - the current (invalid) value.
126 1
func (c LengthConstraint) WithMaxMessage(template string, parameters ...validation.TemplateParameter) LengthConstraint {
127 1
	c.maxMessageTemplate = template
128
	c.maxMessageParameters = parameters
129
	return c
130
}
131
132
// WithExactMessage sets the violation message that will be shown if minimum and maximum values are equal and
133
// the length of the string is not exactly this value. You can set custom template parameters
134
// for injecting its values into the final message. Also, you can use default parameters:
135
//
136
//	{{ length }} - the current string length;
137
//	{{ limit }} - the lower limit;
138 1
//	{{ value }} - the current (invalid) value.
139 1
func (c LengthConstraint) WithExactMessage(template string, parameters ...validation.TemplateParameter) LengthConstraint {
140 1
	c.exactMessageTemplate = template
141
	c.exactMessageParameters = parameters
142
	return c
143
}
144
145
func (c LengthConstraint) ValidateString(ctx context.Context, validator *validation.Validator, value *string) error {
146
	if c.isIgnored || validator.IsIgnoredForGroups(c.groups...) || value == nil || *value == "" {
147
		return nil
148
	}
149
150
	count := utf8.RuneCountInString(*value)
151 1
152 1
	if c.checkMax && count > c.max {
153 1
		return c.newViolation(ctx, validator, count, c.max, *value, c.maxErr, c.maxMessageTemplate, c.maxMessageParameters)
154
	}
155
	if c.checkMin && count < c.min {
156
		return c.newViolation(ctx, validator, count, c.min, *value, c.minErr, c.minMessageTemplate, c.minMessageParameters)
157 1
	}
158 1
159
	return nil
160
}
161 1
162
func (c LengthConstraint) newViolation(
163 1
	ctx context.Context,
164 1
	validator *validation.Validator,
165
	count, limit int,
166 1
	value string,
167 1
	err error,
168
	template string,
169
	parameters validation.TemplateParameterList,
170 1
) validation.Violation {
171
	if c.checkMin && c.checkMax && c.min == c.max {
172
		template = c.exactMessageTemplate
173
		parameters = c.exactMessageParameters
174
		err = c.exactErr
175
	}
176
177
	return validator.BuildViolation(ctx, err, template).
178
		WithPluralCount(limit).
179 1
		WithParameters(
180 1
			parameters.Prepend(
181 1
				validation.TemplateParameter{Key: "{{ value }}", Value: strconv.Quote(value)},
182 1
				validation.TemplateParameter{Key: "{{ length }}", Value: strconv.Itoa(count)},
183
				validation.TemplateParameter{Key: "{{ limit }}", Value: strconv.Itoa(limit)},
184
			)...,
185 1
		).
186
		Create()
187
}
188
189
// RegexpConstraint is used to ensure that the given value corresponds to regex pattern.
190
type RegexpConstraint struct {
191
	isIgnored         bool
192
	match             bool
193
	groups            []string
194
	err               error
195
	messageTemplate   string
196
	messageParameters validation.TemplateParameterList
197
	regex             *regexp.Regexp
198
}
199
200
// Matches creates a [RegexpConstraint] for checking whether a value matches a regular expression.
201
func Matches(regex *regexp.Regexp) RegexpConstraint {
202
	return RegexpConstraint{
203
		regex:           regex,
204
		match:           true,
205
		err:             validation.ErrNotValid,
206
		messageTemplate: validation.ErrNotValid.Message(),
207
	}
208
}
209
210 1
// DoesNotMatch creates a [RegexpConstraint] for checking whether a value does not match a regular expression.
211
func DoesNotMatch(regex *regexp.Regexp) RegexpConstraint {
212
	return RegexpConstraint{
213
		regex:           regex,
214
		match:           false,
215
		err:             validation.ErrNotValid,
216
		messageTemplate: validation.ErrNotValid.Message(),
217
	}
218
}
219
220 1
// WithError overrides default error for produced violation.
221
func (c RegexpConstraint) WithError(err error) RegexpConstraint {
222
	c.err = err
223
	return c
224
}
225
226
// WithMessage sets the violation message template. You can set custom template parameters
227
// for injecting its values into the final message. Also, you can use default parameters:
228
//
229
//	{{ value }} - the current (invalid) value.
230 1
func (c RegexpConstraint) WithMessage(template string, parameters ...validation.TemplateParameter) RegexpConstraint {
231 1
	c.messageTemplate = template
232
	c.messageParameters = parameters
233
	return c
234 1
}
235
236
// When enables conditional validation of this constraint. If the expression evaluates to false,
237
// then the constraint will be ignored.
238
func (c RegexpConstraint) When(condition bool) RegexpConstraint {
239 1
	c.isIgnored = !condition
240
	return c
241
}
242
243
// WhenGroups enables conditional validation of the constraint by using the validation groups.
244 1
func (c RegexpConstraint) WhenGroups(groups ...string) RegexpConstraint {
245 1
	c.groups = groups
246
	return c
247
}
248
249
func (c RegexpConstraint) ValidateString(ctx context.Context, validator *validation.Validator, value *string) error {
250
	if c.regex == nil {
251
		return validator.CreateConstraintError("RegexpConstraint", "nil regex")
252
	}
253 1
	if c.isIgnored || validator.IsIgnoredForGroups(c.groups...) || value == nil || *value == "" {
254 1
		return nil
255 1
	}
256
	if c.match == c.regex.MatchString(*value) {
257
		return nil
258
	}
259
260
	return validator.
261 1
		BuildViolation(ctx, c.err, c.messageTemplate).
262 1
		WithParameters(
263
			c.messageParameters.Prepend(
264
				validation.TemplateParameter{Key: "{{ value }}", Value: *value},
265
			)...,
266
		).
267 1
		Create()
268 1
}
269
270
// IsJSON validates that a value is a valid JSON.
271
func IsJSON() validation.StringFuncConstraint {
272 1
	return validation.OfStringBy(is.JSON).
273 1
		WithError(validation.ErrInvalidJSON).
274
		WithMessage(validation.ErrInvalidJSON.Message())
275 1
}
276 1
277
// IsInteger checks that string value is an integer.
278
func IsInteger() validation.StringFuncConstraint {
279 1
	return validation.OfStringBy(is.Integer).
280
		WithError(validation.ErrNotInteger).
281
		WithMessage(validation.ErrNotInteger.Message())
282
}
283
284
// IsNumeric checks that string value is a valid numeric (integer or float).
285
func IsNumeric() validation.StringFuncConstraint {
286
	return validation.OfStringBy(is.Number).
287
		WithError(validation.ErrNotNumeric).
288
		WithMessage(validation.ErrNotNumeric.Message())
289
}
290