Passed
Pull Request — main (#52)
by Igor
02:16
created

validation.newCountableValidator   A

Complexity

Conditions 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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