Test Setup Failed
Pull Request — main (#57)
by Igor
01:27
created

validation.newEachStringValidator   A

Complexity

Conditions 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

Changes 0
Metric Value
cc 4
eloc 11
nop 2
dl 0
loc 15
rs 9.85
c 0
b 0
f 0
ccs 6
cts 7
cp 0.8571
crap 4.0466
1
// Copyright 2021 Igor Lazarev. All rights reserved.
2
// Use of this source code is governed by a MIT-style
3
// license that can be found in the LICENSE file.
4
5
// Package validation provides tools for data validation.
6
// It is designed to create complex validation rules with abilities to hook into the validation process.
7
package validation
8
9
import (
10
	"context"
11
	"reflect"
12
	"time"
13
14
	"github.com/muonsoft/validation/generic"
15
)
16
17
// Validatable is interface for creating validatable types on the client side.
18
// By using it you can build complex validation rules on a set of objects used in other objects.
19
//
20
// Example
21
//  type Book struct {
22
//      Title    string
23
//      Author   string
24
//      Keywords []string
25
//  }
26
//
27
//  func (b Book) Validate(ctx context.Context, validator *validation.Validator) error {
28
//      return validator.Validate(
29
//          ctx,
30
//          validation.StringProperty("title", &b.Title, it.IsNotBlank()),
31
//          validation.StringProperty("author", &b.Author, it.IsNotBlank()),
32
//          validation.CountableProperty("keywords", len(b.Keywords), it.HasCountBetween(1, 10)),
33
//          validation.EachStringProperty("keywords", b.Keywords, it.IsNotBlank()),
34
//      )
35
//  }
36
type Validatable interface {
37
	Validate(ctx context.Context, validator *Validator) error
38
}
39
40
// Filter is used for processing the list of errors to return a single ViolationList.
41 1
// If there is at least one non-violation error it will return it instead.
42
func Filter(violations ...error) error {
43 1
	list := &ViolationList{}
44 1
45 1
	for _, violation := range violations {
46 1
		err := list.AppendFromError(violation)
47
		if err != nil {
48
			return err
49
		}
50 1
	}
51
52
	return list.AsError()
53
}
54
55
// ValidateByConstraintFunc is used for building validation functions for the values of specific types.
56
type ValidateByConstraintFunc func(constraint Constraint, scope Scope) error
57
58
type validateFunc func(scope Scope) (*ViolationList, error)
59
60
var validatableType = reflect.TypeOf((*Validatable)(nil)).Elem()
61 1
62
func newValueValidator(value interface{}, options []Option) (validateFunc, error) {
63 1
	switch v := value.(type) {
64
	case Validatable:
65 1
		return newValidValidator(v, options), nil
66
	case time.Time:
67 1
		return newTimeValidator(&v, options), nil
68
	case *time.Time:
69
		return newTimeValidator(v, options), nil
70 1
	}
71
72 1
	v := reflect.ValueOf(value)
73
74 1
	switch v.Kind() {
75
	case reflect.Ptr:
76 1
		return newValuePointerValidator(v, options)
77 1
	case reflect.Bool:
78
		b := v.Bool()
79
		return newBoolValidator(&b, options), nil
80
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
81 1
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
82 1
		reflect.Float32, reflect.Float64:
83
		n, err := generic.NewNumber(value)
84
		if err != nil {
85
			return nil, err
86 1
		}
87
88 1
		return newNumberValidator(*n, options), nil
89 1
	case reflect.String:
90
		s := v.String()
91 1
		return newStringValidator(&s, options), nil
92 1
	case reflect.Array, reflect.Slice, reflect.Map:
93
		i, err := generic.NewIterable(value)
94
		if err != nil {
95
			return nil, err
96 1
		}
97
98
		return newIterableValidator(i, options), nil
99 1
	}
100
101
	return nil, &NotValidatableError{Value: v}
102
}
103 1
104 1
func newValuePointerValidator(value reflect.Value, options []Option) (validateFunc, error) {
105 1
	p := value.Elem()
106
	if value.IsNil() {
107
		return newNilValidator(options), nil
108 1
	}
109
110 1
	switch p.Kind() {
111 1
	case reflect.Bool:
112
		b := p.Bool()
113
		return newBoolValidator(&b, options), nil
114
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
115 1
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
116 1
		reflect.Float32, reflect.Float64:
117
		n, err := generic.NewNumber(p.Interface())
118
		if err != nil {
119
			return nil, err
120 1
		}
121
122 1
		return newNumberValidator(*n, options), nil
123 1
	case reflect.String:
124
		s := p.String()
125 1
		return newStringValidator(&s, options), nil
126 1
	case reflect.Array, reflect.Slice, reflect.Map:
127
		i, err := generic.NewIterable(p.Interface())
128
		if err != nil {
129
			return nil, err
130 1
		}
131
132
		return newIterableValidator(i, options), nil
133
	}
134
135
	return nil, &NotValidatableError{Value: value}
136
}
137 1
138 1
func newNilValidator(options []Option) validateFunc {
139 1
	return newValidator(options, func(constraint Constraint, scope Scope) error {
140
		if constraintValidator, ok := constraint.(NilConstraint); ok {
141
			return constraintValidator.ValidateNil(scope)
142
		}
143
144
		return nil
145
	})
146
}
147 1
148 1
func newBoolValidator(value *bool, options []Option) validateFunc {
149 1
	return newValidator(options, func(constraint Constraint, scope Scope) error {
150
		if c, ok := constraint.(BoolConstraint); ok {
151
			return c.ValidateBool(value, scope)
152 1
		}
153
154
		return NewInapplicableConstraintError(constraint, "bool")
155
	})
156
}
157 1
158 1
func newNumberValidator(value generic.Number, options []Option) validateFunc {
159 1
	return newValidator(options, func(constraint Constraint, scope Scope) error {
160
		if c, ok := constraint.(NumberConstraint); ok {
161
			return c.ValidateNumber(value, scope)
162 1
		}
163
164
		return NewInapplicableConstraintError(constraint, "number")
165
	})
166
}
167 1
168 1
func newStringValidator(value *string, options []Option) validateFunc {
169 1
	return newValidator(options, func(constraint Constraint, scope Scope) error {
170
		if c, ok := constraint.(StringConstraint); ok {
171
			return c.ValidateString(value, scope)
172 1
		}
173
174
		return NewInapplicableConstraintError(constraint, "string")
175
	})
176
}
177 1
178 1
func newStringsValidator(values []string, options []Option) validateFunc {
179 1
	return newValidator(options, func(constraint Constraint, scope Scope) error {
180
		if c, ok := constraint.(StringsConstraint); ok {
181
			return c.ValidateStrings(values, scope)
182
		}
183 1
184 1
		return NewInapplicableConstraintError(constraint, "strings")
185 1
	})
186
}
187
188 1
func newIterableValidator(iterable generic.Iterable, options []Option) validateFunc {
189
	return func(scope Scope) (*ViolationList, error) {
190 1
		err := scope.applyOptions(options...)
191 1
		if err != nil {
192
			return nil, err
193
		}
194 1
195 1
		violations, err := validateOnScope(scope, options, func(constraint Constraint, scope Scope) error {
196 1
			if c, ok := constraint.(IterableConstraint); ok {
197
				return c.ValidateIterable(iterable, scope)
198
			}
199 1
200
			return NewInapplicableConstraintError(constraint, "iterable")
201
		})
202 1
		if err != nil {
203
			return nil, err
204
		}
205
206
		if iterable.IsElementImplements(validatableType) {
207 1
			vs, err := validateIterableOfValidatables(scope, iterable)
208 1
			if err != nil {
209 1
				return nil, err
210
			}
211
			violations.Join(vs)
212 1
		}
213
214
		return violations, nil
215
	}
216
}
217 1
218 1
func newCountableValidator(count int, options []Option) validateFunc {
219 1
	return newValidator(options, func(constraint Constraint, scope Scope) error {
220
		if c, ok := constraint.(CountableConstraint); ok {
221
			return c.ValidateCountable(count, scope)
222 1
		}
223
224
		return NewInapplicableConstraintError(constraint, "countable")
225
	})
226
}
227 1
228 1
func newTimeValidator(value *time.Time, options []Option) validateFunc {
229
	return newValidator(options, func(constraint Constraint, scope Scope) error {
230 1
		if c, ok := constraint.(TimeConstraint); ok {
231 1
			return c.ValidateTime(value, scope)
232 1
		}
233 1
234
		return NewInapplicableConstraintError(constraint, "time")
235 1
	})
236
}
237
238 1
func newEachValidator(iterable generic.Iterable, options []Option) validateFunc {
239 1
	return func(scope Scope) (*ViolationList, error) {
240
		violations := &ViolationList{}
241
242
		err := iterable.Iterate(func(key generic.Key, value interface{}) error {
243 1
			opts := options
244 1
			if key.IsIndex() {
245
				opts = append(opts, ArrayIndex(key.Index()))
246
			} else {
247 1
				opts = append(opts, PropertyName(key.String()))
248
			}
249 1
250
			validate, err := newValueValidator(value, opts)
251
			if err != nil {
252 1
				return err
253
			}
254
255
			vs, err := validate(scope)
256
			if err != nil {
257 1
				return err
258 1
			}
259
			violations.Join(vs)
260 1
261 1
			return nil
262 1
		})
263 1
264 1
		return violations, err
265
	}
266
}
267 1
268
func newEachStringValidator(values []string, options []Option) validateFunc {
269
	return func(scope Scope) (*ViolationList, error) {
270 1
		violations := &ViolationList{}
271
272
		for i := range values {
273
			opts := append(options, ArrayIndex(i))
274
			validate := newStringValidator(&values[i], opts)
275 1
			vs, err := validate(scope)
276 1
			if err != nil {
277 1
				return nil, err
278
			}
279
			violations.Join(vs)
280
		}
281 1
282 1
		return violations, nil
283 1
	}
284 1
}
285
286
func newValidValidator(value Validatable, options []Option) validateFunc {
287 1
	return func(scope Scope) (*ViolationList, error) {
288
		err := scope.applyOptions(options...)
289
		if err != nil {
290
			return nil, err
291
		}
292 1
293 1
		err = value.Validate(scope.context, newScopedValidator(scope))
294 1
		violations, ok := UnwrapViolationList(err)
295 1
		if ok {
296
			return violations, nil
297
		}
298 1
299
		return nil, err
300
	}
301
}
302
303 1
func newValidator(options []Option, validate ValidateByConstraintFunc) validateFunc {
304
	return func(scope Scope) (*ViolationList, error) {
305 1
		err := scope.applyOptions(options...)
306 1
		if err != nil {
307 1
			return nil, err
308 1
		}
309
310
		return validateOnScope(scope, options, validate)
311
	}
312 1
}
313 1
314 1
func validateOnScope(scope Scope, options []Option, validate ValidateByConstraintFunc) (*ViolationList, error) {
315 1
	violations := &ViolationList{}
316 1
317
	for _, option := range options {
318
		if constraint, ok := option.(controlConstraint); ok {
319
			vs, err := constraint.validate(scope, validate)
320
			if err != nil {
321 1
				return nil, err
322
			}
323
324
			violations.Join(vs)
325 1
		} else if constraint, ok := option.(Constraint); ok {
326
			err := violations.AppendFromError(validate(constraint, scope))
327 1
			if err != nil {
328 1
				return nil, err
329 1
			}
330 1
		}
331
	}
332 1
333
	return violations, nil
334
}
335 1
336 1
func validateIterableOfValidatables(scope Scope, iterable generic.Iterable) (*ViolationList, error) {
337 1
	violations := &ViolationList{}
338
339
	err := iterable.Iterate(func(key generic.Key, value interface{}) error {
340
		s := scope
341 1
		if key.IsIndex() {
342
			s = s.atIndex(key.Index())
343 1
		} else {
344
			s = s.atProperty(key.String())
345
		}
346 1
347
		validate := newValidValidator(value.(Validatable), nil)
348
		vs, err := validate(s)
349
		if err != nil {
350
			return err
351
		}
352
353
		violations.Join(vs)
354
355
		return nil
356
	})
357
358
	return violations, err
359
}
360