Passed
Push — main ( 3f29c3...92079a )
by Rushan
58s queued 10s
created

validation.newValuePointerValidator   B

Complexity

Conditions 8

Size

Total Lines 32
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8.3518

Importance

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