validation.WhenGroups   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
package validation
2
3
import (
4
	"context"
5
	"sync"
6
)
7
8
// WhenArgument is used to build conditional validation. Use the [When] function to initiate a conditional check.
9
// If the condition is true, then the arguments passed through the [WhenArgument.Then] function will be processed.
10
// Otherwise, the arguments passed through the [WhenArgument.Else] function will be processed.
11
type WhenArgument struct {
12
	isTrue        bool
13
	path          []PropertyPathElement
14
	thenArguments []Argument
15
	elseArguments []Argument
16
}
17
18
// When function is used to initiate conditional validation.
19
// If the condition is true, then the arguments passed through the [WhenArgument.Then] function will be processed.
20
// Otherwise, the arguments passed through the [WhenArgument.Else] function will be processed.
21
func When(isTrue bool) WhenArgument {
22
	return WhenArgument{isTrue: isTrue}
23
}
24
25
// Then function is used to set a sequence of arguments to be processed if the condition is true.
26
func (arg WhenArgument) Then(arguments ...Argument) WhenArgument {
27
	arg.thenArguments = arguments
28
	return arg
29
}
30
31
// Else function is used to set a sequence of arguments to be processed if a condition is false.
32
func (arg WhenArgument) Else(arguments ...Argument) WhenArgument {
33
	arg.elseArguments = arguments
34
	return arg
35
}
36
37
// At returns a copy of [WhenArgument] with appended property path suffix.
38
func (arg WhenArgument) At(path ...PropertyPathElement) WhenArgument {
39
	arg.path = append(arg.path, path...)
40
	return arg
41
}
42
43
func (arg WhenArgument) setUp(ctx *executionContext) {
44
	ctx.addValidation(arg.validate, arg.path...)
45
}
46
47
func (arg WhenArgument) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
48
	var err error
49
	if arg.isTrue {
50
		err = validator.Validate(ctx, arg.thenArguments...)
51
	} else {
52
		err = validator.Validate(ctx, arg.elseArguments...)
53
	}
54
55
	return unwrapViolationList(err)
56
}
57
58
// WhenGroupsArgument is used to build conditional validation based on groups. Use the [WhenGroups] function
59
// to initiate a conditional check. If validation group matches to the validator one,
60
// then the arguments passed through the [WhenGroupsArgument.Then] function will be processed.
61
// Otherwise, the arguments passed through the [WhenGroupsArgument.Else] function will be processed.
62
type WhenGroupsArgument struct {
63
	groups        []string
64
	path          []PropertyPathElement
65
	thenArguments []Argument
66
	elseArguments []Argument
67
}
68
69
// WhenGroups is used to build conditional validation based on groups. If validation group matches to
70
// the validator one, then the arguments passed through the [WhenGroupsArgument.Then] function will be processed.
71
// Otherwise, the arguments passed through the [WhenGroupsArgument.Else] function will be processed.
72
func WhenGroups(groups ...string) WhenGroupsArgument {
73
	return WhenGroupsArgument{groups: groups}
74
}
75
76
// Then function is used to set a sequence of arguments to be processed if the validation group is active.
77
func (arg WhenGroupsArgument) Then(arguments ...Argument) WhenGroupsArgument {
78
	arg.thenArguments = arguments
79
	return arg
80
}
81
82
// Else function is used to set a sequence of arguments to be processed if the validation group is active.
83
func (arg WhenGroupsArgument) Else(arguments ...Argument) WhenGroupsArgument {
84
	arg.elseArguments = arguments
85
	return arg
86
}
87
88
// At returns a copy of [WhenGroupsArgument] with appended property path suffix.
89
func (arg WhenGroupsArgument) At(path ...PropertyPathElement) WhenGroupsArgument {
90
	arg.path = append(arg.path, path...)
91
	return arg
92
}
93
94
func (arg WhenGroupsArgument) setUp(ctx *executionContext) {
95
	ctx.addValidation(arg.validate, arg.path...)
96
}
97
98
func (arg WhenGroupsArgument) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
99
	var err error
100
	if validator.IsIgnoredForGroups(arg.groups...) {
101
		err = validator.Validate(ctx, arg.elseArguments...)
102
	} else {
103
		err = validator.Validate(ctx, arg.thenArguments...)
104
	}
105
106
	return unwrapViolationList(err)
107
}
108
109
// SequentialArgument can be used to interrupt validation process when the first violation is raised.
110
type SequentialArgument struct {
111
	isIgnored bool
112
	path      []PropertyPathElement
113
	arguments []Argument
114
}
115
116
// Sequentially function used to run validation process step-by-step.
117
func Sequentially(arguments ...Argument) SequentialArgument {
118
	return SequentialArgument{arguments: arguments}
119
}
120
121
// At returns a copy of [SequentialArgument] with appended property path suffix.
122
func (arg SequentialArgument) At(path ...PropertyPathElement) SequentialArgument {
123
	arg.path = append(arg.path, path...)
124
	return arg
125
}
126
127
// When enables conditional validation of this argument. If the expression evaluates to false,
128
// then the argument will be ignored.
129
func (arg SequentialArgument) When(condition bool) SequentialArgument {
130
	arg.isIgnored = !condition
131
	return arg
132
}
133
134
func (arg SequentialArgument) setUp(ctx *executionContext) {
135
	ctx.addValidation(arg.validate, arg.path...)
136
}
137
138
func (arg SequentialArgument) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
139
	if arg.isIgnored {
140
		return nil, nil
141
	}
142
143
	violations := &ViolationList{}
144
145
	for _, argument := range arg.arguments {
146
		err := violations.AppendFromError(validator.Validate(ctx, argument))
147
		if err != nil {
148
			return nil, err
149
		}
150
		if violations.len > 0 {
151
			return violations, nil
152
		}
153
	}
154
155
	return violations, nil
156
}
157
158
// AtLeastOneOfArgument can be used to set up validation process to check that the value satisfies
159
// at least one of the given constraints. The validation stops as soon as one constraint is satisfied.
160
type AtLeastOneOfArgument struct {
161
	isIgnored bool
162
	path      []PropertyPathElement
163
	arguments []Argument
164
}
165
166
// AtLeastOneOf can be used to set up validation process to check that the value satisfies
167
// at least one of the given constraints. The validation stops as soon as one constraint is satisfied.
168
func AtLeastOneOf(arguments ...Argument) AtLeastOneOfArgument {
169
	return AtLeastOneOfArgument{arguments: arguments}
170
}
171
172
// At returns a copy of [AtLeastOneOfArgument] with appended property path suffix.
173
func (arg AtLeastOneOfArgument) At(path ...PropertyPathElement) AtLeastOneOfArgument {
174
	arg.path = append(arg.path, path...)
175
	return arg
176
}
177
178
// When enables conditional validation of this argument. If the expression evaluates to false,
179
// then the argument will be ignored.
180
func (arg AtLeastOneOfArgument) When(condition bool) AtLeastOneOfArgument {
181
	arg.isIgnored = !condition
182
	return arg
183
}
184
185
func (arg AtLeastOneOfArgument) setUp(ctx *executionContext) {
186
	ctx.addValidation(arg.validate, arg.path...)
187
}
188
189
func (arg AtLeastOneOfArgument) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
190
	if arg.isIgnored {
191
		return nil, nil
192
	}
193
194
	violations := &ViolationList{}
195
196
	for _, argument := range arg.arguments {
197
		violation := validator.Validate(ctx, argument)
198
		if violation == nil {
199
			return nil, nil
200
		}
201
202
		err := violations.AppendFromError(violation)
203
		if err != nil {
204
			return nil, err
205
		}
206
	}
207
208
	return violations, nil
209
}
210
211
// AllArgument can be used to interrupt validation process when the first violation is raised.
212
type AllArgument struct {
213
	isIgnored bool
214
	path      []PropertyPathElement
215
	arguments []Argument
216
}
217
218
// All runs validation for each argument. It works exactly as [Validator.Validate] method.
219
// It can be helpful to build complex validation process.
220
func All(arguments ...Argument) AllArgument {
221
	return AllArgument{arguments: arguments}
222
}
223
224
// AtProperty can be used to group different kind of validations for a specific property.
225
// It creates an AllArgument option and executes validation on all the arguments.
226
func AtProperty(propertyName string, arguments ...Argument) AllArgument {
227
	return All(arguments...).At(PropertyName(propertyName))
228
}
229
230
// At returns a copy of [AllArgument] with appended property path suffix.
231
func (arg AllArgument) At(path ...PropertyPathElement) AllArgument {
232
	arg.path = append(arg.path, path...)
233
	return arg
234
}
235
236
// When enables conditional validation of this argument. If the expression evaluates to false,
237
// then the argument will be ignored.
238
func (arg AllArgument) When(condition bool) AllArgument {
239
	arg.isIgnored = !condition
240
	return arg
241
}
242
243
func (arg AllArgument) setUp(ctx *executionContext) {
244
	ctx.addValidation(arg.validate, arg.path...)
245
}
246
247
func (arg AllArgument) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
248
	if arg.isIgnored {
249
		return nil, nil
250
	}
251
252
	violations := &ViolationList{}
253
254
	for _, argument := range arg.arguments {
255
		err := violations.AppendFromError(validator.Validate(ctx, argument))
256
		if err != nil {
257
			return nil, err
258
		}
259
	}
260
261
	return violations, nil
262
}
263
264
// AsyncArgument can be used to interrupt validation process when the first violation is raised.
265
type AsyncArgument struct {
266
	isIgnored bool
267
	path      []PropertyPathElement
268
	arguments []Argument
269
}
270
271
// Async implements async/await pattern and runs validation for each argument in a separate goroutine.
272
func Async(arguments ...Argument) AsyncArgument {
273
	return AsyncArgument{arguments: arguments}
274
}
275
276
// At returns a copy of [AsyncArgument] with appended property path suffix.
277
func (arg AsyncArgument) At(path ...PropertyPathElement) AsyncArgument {
278
	arg.path = append(arg.path, path...)
279
	return arg
280
}
281
282
// When enables conditional validation of this argument. If the expression evaluates to false,
283
// then the argument will be ignored.
284
func (arg AsyncArgument) When(condition bool) AsyncArgument {
285
	arg.isIgnored = !condition
286
	return arg
287
}
288
289
func (arg AsyncArgument) setUp(ctx *executionContext) {
290
	ctx.addValidation(arg.validate, arg.path...)
291
}
292
293
func (arg AsyncArgument) validate(ctx context.Context, validator *Validator) (*ViolationList, error) {
294
	if arg.isIgnored {
295
		return nil, nil
296
	}
297
298
	ctx, cancel := context.WithCancel(ctx)
299
	defer cancel()
300
301
	waiter := &sync.WaitGroup{}
302
	waiter.Add(len(arg.arguments))
303
	errs := make(chan error)
304
	for _, argument := range arg.arguments {
305
		go func(argument Argument) {
306
			defer waiter.Done()
307
			errs <- validator.Validate(ctx, argument)
308
		}(argument)
309
	}
310
311
	go func() {
312
		waiter.Wait()
313
		close(errs)
314
	}()
315
316
	violations := &ViolationList{}
317
318
	for violation := range errs {
319
		err := violations.AppendFromError(violation)
320
		if err != nil {
321
			return nil, err
322
		}
323
	}
324
325
	return violations, nil
326
}
327