Test Failed
Pull Request — main (#74)
by Igor
02:02
created

it.RegexpConstraint.ValidateString   B

Complexity

Conditions 7

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 7

Importance

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