1 | package validation |
||
2 | |||
3 | import ( |
||
4 | "context" |
||
5 | "time" |
||
6 | ) |
||
7 | |||
8 | // Numeric is used as a type parameter for numeric values. |
||
9 | type Numeric interface { |
||
10 | ~float32 | ~float64 | |
||
11 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | |
||
12 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
||
13 | } |
||
14 | |||
15 | // Constraint is a generic interface for client-side typed constraints. |
||
16 | type Constraint[T any] interface { |
||
17 | Validate(ctx context.Context, validator *Validator, v T) error |
||
18 | } |
||
19 | |||
20 | // NilConstraint is used for a special cases to check a value for nil. |
||
21 | type NilConstraint interface { |
||
22 | ValidateNil(ctx context.Context, validator *Validator, isNil bool) error |
||
23 | } |
||
24 | |||
25 | // BoolConstraint is used to build constraints for boolean values validation. |
||
26 | type BoolConstraint interface { |
||
27 | ValidateBool(ctx context.Context, validator *Validator, value *bool) error |
||
28 | } |
||
29 | |||
30 | // NumberConstraint is used to build constraints for numeric values validation. |
||
31 | type NumberConstraint[T Numeric] interface { |
||
32 | ValidateNumber(ctx context.Context, validator *Validator, value *T) error |
||
33 | } |
||
34 | |||
35 | // StringConstraint is used to build constraints for string values validation. |
||
36 | type StringConstraint interface { |
||
37 | ValidateString(ctx context.Context, validator *Validator, value *string) error |
||
38 | } |
||
39 | |||
40 | // ComparableConstraint is used to build constraints for generic comparable value validation. |
||
41 | type ComparableConstraint[T comparable] interface { |
||
42 | ValidateComparable(ctx context.Context, validator *Validator, value *T) error |
||
43 | } |
||
44 | |||
45 | // ComparablesConstraint is used to build constraints for generic comparable values validation. |
||
46 | type ComparablesConstraint[T comparable] interface { |
||
47 | ValidateComparables(ctx context.Context, validator *Validator, values []T) error |
||
48 | } |
||
49 | |||
50 | // CountableConstraint is used to build constraints for simpler validation of iterable elements count. |
||
51 | type CountableConstraint interface { |
||
52 | ValidateCountable(ctx context.Context, validator *Validator, count int) error |
||
53 | } |
||
54 | |||
55 | // TimeConstraint is used to build constraints for date/time validation. |
||
56 | type TimeConstraint interface { |
||
57 | ValidateTime(ctx context.Context, validator *Validator, value *time.Time) error |
||
58 | } |
||
59 | |||
60 | // StringFuncConstraint can be used to create constraints for validating string values |
||
61 | // based on function with signature func(string) bool. |
||
62 | type StringFuncConstraint struct { |
||
63 | isIgnored bool |
||
64 | isValid func(string) bool |
||
65 | groups []string |
||
66 | err error |
||
67 | messageTemplate string |
||
68 | messageParameters TemplateParameterList |
||
69 | } |
||
70 | |||
71 | // OfStringBy creates a new string constraint from a function with signature func(string) bool. |
||
72 | func OfStringBy(isValid func(string) bool) StringFuncConstraint { |
||
73 | return StringFuncConstraint{ |
||
74 | isValid: isValid, |
||
75 | err: ErrNotValid, |
||
76 | messageTemplate: ErrNotValid.Message(), |
||
77 | } |
||
78 | } |
||
79 | |||
80 | // WithError overrides default error for produced violation. |
||
81 | func (c StringFuncConstraint) WithError(err error) StringFuncConstraint { |
||
82 | c.err = err |
||
83 | return c |
||
84 | } |
||
85 | |||
86 | // WithMessage sets the violation message template. You can set custom template parameters |
||
87 | // for injecting its values into the final message. Also, you can use default parameters: |
||
88 | // |
||
89 | // {{ value }} - the current (invalid) value. |
||
90 | func (c StringFuncConstraint) WithMessage(template string, parameters ...TemplateParameter) StringFuncConstraint { |
||
91 | c.messageTemplate = template |
||
92 | c.messageParameters = parameters |
||
93 | 1 | return c |
|
94 | } |
||
95 | |||
96 | // When enables conditional validation of this constraint. If the expression evaluates to false, |
||
97 | // then the constraint will be ignored. |
||
98 | func (c StringFuncConstraint) When(condition bool) StringFuncConstraint { |
||
99 | c.isIgnored = !condition |
||
100 | 1 | return c |
|
101 | 1 | } |
|
102 | |||
103 | 1 | // WhenGroups enables conditional validation of the constraint by using the validation groups. |
|
104 | 1 | func (c StringFuncConstraint) WhenGroups(groups ...string) StringFuncConstraint { |
|
105 | c.groups = groups |
||
106 | 1 | return c |
|
107 | 1 | } |
|
108 | |||
109 | func (c StringFuncConstraint) ValidateString(ctx context.Context, validator *Validator, value *string) error { |
||
110 | 1 | if c.isIgnored || validator.IsIgnoredForGroups(c.groups...) || value == nil || *value == "" || c.isValid(*value) { |
|
111 | return nil |
||
112 | } |
||
113 | |||
114 | return validator.BuildViolation(ctx, c.err, c.messageTemplate). |
||
115 | 1 | WithParameters( |
|
116 | c.messageParameters.Prepend( |
||
117 | TemplateParameter{Key: "{{ value }}", Value: *value}, |
||
118 | )..., |
||
119 | ). |
||
120 | 1 | WithParameter("{{ value }}", *value). |
|
121 | Create() |
||
122 | } |
||
123 |