Code

< 40 %
40-60 %
> 60 %
1
package validation
2
3
import (
4
	"context"
5
	"time"
6
)
7
8
// Argument used to set up the validation process. It is used to set up the current validation context and to pass
9
// arguments for validation values.
10
type Argument interface {
11
	setUp(ctx *executionContext)
12
}
13
14
// Nil argument is used to validate nil values of any nillable types.
15
func Nil(isNil bool, constraints ...NilConstraint) ValidatorArgument {
16
	return NewArgument(validateNil(isNil, constraints))
17
}
18
19
// NilProperty argument is an alias for [Nil] that automatically adds property name to the current validation context.
20
func NilProperty(name string, isNil bool, constraints ...NilConstraint) ValidatorArgument {
21
	return NewArgument(validateNil(isNil, constraints)).At(PropertyName(name))
22 1
}
23
24
// Bool argument is used to validate boolean values.
25
func Bool(value bool, constraints ...BoolConstraint) ValidatorArgument {
26
	return NewArgument(validateBool(&value, constraints))
27
}
28
29
// BoolProperty argument is an alias for [Bool] that automatically adds property name to the current validation context.
30
func BoolProperty(name string, value bool, constraints ...BoolConstraint) ValidatorArgument {
31 1
	return NewArgument(validateBool(&value, constraints)).At(PropertyName(name))
32
}
33
34
// NilBool argument is used to validate nillable boolean values.
35
func NilBool(value *bool, constraints ...BoolConstraint) ValidatorArgument {
36
	return NewArgument(validateBool(value, constraints))
37
}
38
39
// NilBoolProperty argument is an alias for [NilBool] that automatically adds property name to the current validation context.
40 1
func NilBoolProperty(name string, value *bool, constraints ...BoolConstraint) ValidatorArgument {
41 1
	return NewArgument(validateBool(value, constraints)).At(PropertyName(name))
42 1
}
43 1
44
// Number argument is used to validate numbers.
45
func Number[T Numeric](value T, constraints ...NumberConstraint[T]) ValidatorArgument {
46 1
	return NewArgument(validateNumber(&value, constraints))
47
}
48 1
49
// NumberProperty argument is an alias for [Number] that automatically adds property name to the current validation context.
50
func NumberProperty[T Numeric](name string, value T, constraints ...NumberConstraint[T]) ValidatorArgument {
51
	return NewArgument(validateNumber(&value, constraints)).At(PropertyName(name))
52
}
53
54 1
// NilNumber argument is used to validate nillable numbers.
55
func NilNumber[T Numeric](value *T, constraints ...NumberConstraint[T]) ValidatorArgument {
56
	return NewArgument(validateNumber(value, constraints))
57
}
58
59 1
// NilNumberProperty argument is an alias for [NilNumber] that automatically adds property name to the current validation context.
60 1
func NilNumberProperty[T Numeric](name string, value *T, constraints ...NumberConstraint[T]) ValidatorArgument {
61
	return NewArgument(validateNumber(value, constraints)).At(PropertyName(name))
62 1
}
63
64
// String argument is used to validate strings.
65
func String(value string, constraints ...StringConstraint) ValidatorArgument {
66
	return NewArgument(validateString(&value, constraints))
67
}
68 1
69
// StringProperty argument is an alias for [String] that automatically adds property name to the current validation context.
70
func StringProperty(name string, value string, constraints ...StringConstraint) ValidatorArgument {
71
	return NewArgument(validateString(&value, constraints)).At(PropertyName(name))
72
}
73 1
74 1
// NilString argument is used to validate nillable strings.
75
func NilString(value *string, constraints ...StringConstraint) ValidatorArgument {
76 1
	return NewArgument(validateString(value, constraints))
77
}
78
79
// NilStringProperty argument is an alias for [NilString] that automatically adds property name to the current validation context.
80
func NilStringProperty(name string, value *string, constraints ...StringConstraint) ValidatorArgument {
81
	return NewArgument(validateString(value, constraints)).At(PropertyName(name))
82 1
}
83
84
// Countable argument can be used to validate size of an array, slice, or map. You can pass result of len()
85
// function as an argument.
86
func Countable(count int, constraints ...CountableConstraint) ValidatorArgument {
87
	return NewArgument(validateCountable(count, constraints))
88
}
89
90 1
// CountableProperty argument is an alias for [Countable] that automatically adds property name to the current validation context.
91 1
func CountableProperty(name string, count int, constraints ...CountableConstraint) ValidatorArgument {
92 1
	return NewArgument(validateCountable(count, constraints)).At(PropertyName(name))
93 1
}
94
95
// Time argument is used to validate [time.Time] value.
96 1
func Time(value time.Time, constraints ...TimeConstraint) ValidatorArgument {
97
	return NewArgument(validateTime(&value, constraints))
98 1
}
99
100
// TimeProperty argument is an alias for [Time] that automatically adds property name to the current validation context.
101
func TimeProperty(name string, value time.Time, constraints ...TimeConstraint) ValidatorArgument {
102
	return NewArgument(validateTime(&value, constraints)).At(PropertyName(name))
103
}
104 1
105
// NilTime argument is used to validate nillable [time.Time] value.
106
func NilTime(value *time.Time, constraints ...TimeConstraint) ValidatorArgument {
107
	return NewArgument(validateTime(value, constraints))
108
}
109 1
110 1
// NilTimeProperty argument is an alias for [NilTime] that automatically adds property name to the current validation context.
111
func NilTimeProperty(name string, value *time.Time, constraints ...TimeConstraint) ValidatorArgument {
112 1
	return NewArgument(validateTime(value, constraints)).At(PropertyName(name))
113
}
114
115
// Valid is used to run validation on the [Validatable] type. This method is recommended
116
// to build a complex validation process.
117
func Valid(value Validatable) ValidatorArgument {
118 1
	return NewArgument(validateIt(value))
119
}
120
121
// ValidProperty argument is an alias for [Valid] that automatically adds property name to the current validation context.
122
func ValidProperty(name string, value Validatable) ValidatorArgument {
123 1
	return NewArgument(validateIt(value)).At(PropertyName(name))
124 1
}
125
126 1
// ValidSlice is a generic argument used to run validation on the slice of [Validatable] types.
127
// This method is recommended to build a complex validation process.
128
func ValidSlice[T Validatable](values []T) ValidatorArgument {
129
	return NewArgument(validateSlice(values))
130
}
131
132 1
// ValidSliceProperty argument is an alias for [ValidSlice] that automatically adds property name to the current validation context.
133
func ValidSliceProperty[T Validatable](name string, values []T) ValidatorArgument {
134
	return NewArgument(validateSlice(values)).At(PropertyName(name))
135
}
136
137 1
// ValidMap is a generic argument used to run validation on the map of [Validatable] types.
138 1
// This method is recommended to build a complex validation process.
139
func ValidMap[T Validatable](values map[string]T) ValidatorArgument {
140 1
	return NewArgument(validateMap(values))
141
}
142
143
// ValidMapProperty argument is an alias for [ValidSlice] that automatically adds property name to the current validation context.
144
func ValidMapProperty[T Validatable](name string, values map[string]T) ValidatorArgument {
145
	return NewArgument(validateMap(values)).At(PropertyName(name))
146 1
}
147
148
// Comparable argument is used to validate generic comparable value.
149
func Comparable[T comparable](value T, constraints ...ComparableConstraint[T]) ValidatorArgument {
150
	return NewArgument(validateComparable(&value, constraints))
151
}
152
153
// ComparableProperty argument is an alias for [Comparable] that automatically adds property name to the current validation context.
154
func ComparableProperty[T comparable](name string, value T, constraints ...ComparableConstraint[T]) ValidatorArgument {
155 1
	return NewArgument(validateComparable(&value, constraints)).At(PropertyName(name))
156 1
}
157 1
158 1
// NilComparable argument is used to validate nillable generic comparable value.
159
func NilComparable[T comparable](value *T, constraints ...ComparableConstraint[T]) ValidatorArgument {
160
	return NewArgument(validateComparable(value, constraints))
161 1
}
162
163 1
// NilComparableProperty argument is an alias for [NilComparable] that automatically adds property name to the current validation context.
164
func NilComparableProperty[T comparable](name string, value *T, constraints ...ComparableConstraint[T]) ValidatorArgument {
165
	return NewArgument(validateComparable(value, constraints)).At(PropertyName(name))
166
}
167
168
// Comparables argument is used to validate generic comparable types.
169 1
func Comparables[T comparable](values []T, constraints ...ComparablesConstraint[T]) ValidatorArgument {
170
	return NewArgument(validateComparables(values, constraints))
171
}
172
173
// ComparablesProperty argument is an alias for [Comparables] that automatically adds property name to the current validation context.
174
func ComparablesProperty[T comparable](name string, values []T, constraints ...ComparablesConstraint[T]) ValidatorArgument {
175 1
	return NewArgument(validateComparables(values, constraints)).At(PropertyName(name))
176 1
}
177
178 1
// EachString is used to validate a slice of strings.
179
func EachString(values []string, constraints ...StringConstraint) ValidatorArgument {
180
	return NewArgument(validateEachString(values, constraints))
181
}
182
183
// EachStringProperty argument is an alias for [EachString] that automatically adds property name to the current validation context.
184 1
func EachStringProperty(name string, values []string, constraints ...StringConstraint) ValidatorArgument {
185
	return NewArgument(validateEachString(values, constraints)).At(PropertyName(name))
186
}
187
188
// EachNumber is used to validate a slice of numbers.
189 1
func EachNumber[T Numeric](values []T, constraints ...NumberConstraint[T]) ValidatorArgument {
190 1
	return NewArgument(validateEachNumber(values, constraints))
191
}
192 1
193
// EachNumberProperty argument is an alias for [EachNumber] that automatically adds property name to the current validation context.
194
func EachNumberProperty[T Numeric](name string, values []T, constraints ...NumberConstraint[T]) ValidatorArgument {
195
	return NewArgument(validateEachNumber(values, constraints)).At(PropertyName(name))
196
}
197
198 1
// EachComparable is used to validate a slice of generic comparables.
199
func EachComparable[T comparable](values []T, constraints ...ComparableConstraint[T]) ValidatorArgument {
200
	return NewArgument(validateEachComparable(values, constraints))
201
}
202
203 1
// EachComparableProperty argument is an alias for [EachComparable] that automatically adds property name to the current validation context.
204 1
func EachComparableProperty[T comparable](name string, values []T, constraints ...ComparableConstraint[T]) ValidatorArgument {
205
	return NewArgument(validateEachComparable(values, constraints)).At(PropertyName(name))
206 1
}
207
208
// CheckNoViolations is a special argument that checks err for violations. If err contains [Violation] or [ViolationList]
209
// then these violations will be appended into returned violation list from the validator. If err contains an error
210
// that does not implement an error interface, then the validation process will be terminated and
211
// this error will be returned.
212 1
func CheckNoViolations(err error) ValidatorArgument {
213
	return NewArgument(func(ctx context.Context, validator *Validator) (*ViolationList, error) {
214
		return unwrapViolationList(err)
215
	})
216
}
217
218
// Check argument can be useful for quickly checking the result of some simple expression
219
// that returns a boolean value.
220
func Check(isValid bool) Checker {
221
	return Checker{
222 1
		isValid:         isValid,
223 1
		err:             ErrNotValid,
224 1
		messageTemplate: ErrNotValid.Message(),
225 1
	}
226
}
227
228 1
// CheckProperty argument is an alias for [Check] that automatically adds property name to the current validation context.
229
// It is useful to apply a simple checks on structs.
230 1
func CheckProperty(name string, isValid bool) Checker {
231
	return Check(isValid).At(PropertyName(name))
232
}
233
234
type ValidateFunc func(ctx context.Context, validator *Validator) (*ViolationList, error)
235
236 1
// NewArgument can be used to implement validation functional arguments for the specific types.
237
func NewArgument(validate ValidateFunc) ValidatorArgument {
238
	return ValidatorArgument{validate: validate}
239
}
240
241 1
// This creates a generic validation argument that can help implement the validation
242 1
// argument for client-side types.
243
func This[T any](v T, constraints ...Constraint[T]) ValidatorArgument {
244 1
	return NewArgument(func(ctx context.Context, validator *Validator) (*ViolationList, error) {
245
		violations := NewViolationList()
246
247
		for _, constraint := range constraints {
248
			err := violations.AppendFromError(constraint.Validate(ctx, validator, v))
249
			if err != nil {
250 1
				return nil, err
251
			}
252
		}
253
254
		return violations, nil
255
	})
256 1
}
257 1
258
// ValidatorArgument is common implementation of [Argument] that is used to run validation
259 1
// process on given argument.
260
type ValidatorArgument struct {
261
	isIgnored bool
262
	validate  ValidateFunc
263
	path      []PropertyPathElement
264
}
265 1
266
// At returns a copy of [ValidatorArgument] with appended property path suffix.
267
func (arg ValidatorArgument) At(path ...PropertyPathElement) ValidatorArgument {
268
	arg.path = append(arg.path, path...)
269
	return arg
270
}
271
272
// When enables conditional validation of this argument. If the expression evaluates to false,
273 1
// then the argument will be ignored.
274 1
func (arg ValidatorArgument) When(condition bool) ValidatorArgument {
275 1
	arg.isIgnored = !condition
276 1
	return arg
277 1
}
278 1
279
func (arg ValidatorArgument) setUp(ctx *executionContext) {
280
	if !arg.isIgnored {
281 1
		ctx.addValidation(arg.validate, arg.path...)
282
	}
283
}
284 1
285
// Checker is an argument that can be useful for quickly checking the result of
286
// some simple expression that returns a boolean value.
287
type Checker struct {
288
	isIgnored         bool
289
	isValid           bool
290
	path              []PropertyPathElement
291 1
	groups            []string
292
	err               error
293
	messageTemplate   string
294
	messageParameters TemplateParameterList
295
}
296
297
// At returns a copy of [Checker] with appended property path suffix.
298
func (c Checker) At(path ...PropertyPathElement) Checker {
299
	c.path = append(c.path, path...)
300
	return c
301 1
}
302
303
// When enables conditional validation of this constraint. If the expression evaluates to false,
304
// then the constraint will be ignored.
305
func (c Checker) When(condition bool) Checker {
306
	c.isIgnored = !condition
307
	return c
308
}
309
310
// WhenGroups enables conditional validation of the constraint by using the validation groups.
311 1
func (c Checker) WhenGroups(groups ...string) Checker {
312 1
	c.groups = groups
313
	return c
314 1
}
315
316
// WithError overrides default code for produced violation.
317
func (c Checker) WithError(err error) Checker {
318
	c.err = err
319
	return c
320
}
321 1
322 1
// WithMessage sets the violation message template. You can set custom template parameters
323
// for injecting its values into the final message.
324 1
func (c Checker) WithMessage(template string, parameters ...TemplateParameter) Checker {
325
	c.messageTemplate = template
326
	c.messageParameters = parameters
327
	return c
328
}
329
330
func (c Checker) setUp(arguments *executionContext) {
331
	arguments.addValidation(c.validate, c.path...)
332
}
333
334
func (c Checker) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
335
	if c.isValid || c.isIgnored || validator.IsIgnoredForGroups(c.groups...) {
336
		return nil, nil
337
	}
338
339
	violation := validator.BuildViolation(ctx, c.err, c.messageTemplate).
340
		WithParameters(c.messageParameters...).
341
		Create()
342
343 1
	return NewViolationList(violation), nil
344
}
345