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 |