Passed
Push — main ( b66d68...556c6a )
by Rushan
02:08 queued 12s
created

validator.go   A

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Test Coverage

Coverage 98.08%

Importance

Changes 0
Metric Value
cc 35
eloc 84
dl 0
loc 243
rs 9.6
c 0
b 0
f 0
ccs 51
cts 52
cp 0.9808
crap 35.0086

23 Methods

Rating   Name   Duplication   Size   Complexity  
A validation.*Validator.AtIndex 0 2 1
A validation.Translations 0 3 2
A validation.*Validator.ValidateEach 0 2 1
A validation.*Validator.ValidateBool 0 2 1
A validation.*Validator.ValidateNumber 0 2 1
A validation.*Validator.ValidateIterable 0 2 1
A validation.*Validator.ValidateString 0 2 1
A validation.NewValidator 0 16 4
A validation.DefaultLanguage 0 4 2
A validation.*Validator.ValidateTime 0 2 1
A validation.*Validator.AtProperty 0 2 1
A validation.*Validator.WithLanguage 0 2 1
A validation.*Validator.ValidateEachString 0 2 1
A validation.*Validator.WithContext 0 2 1
A validation.*Validator.ValidateCountable 0 2 1
A validation.SetViolationFactory 0 5 2
A validation.newScopedValidator 0 2 1
A validation.*Validator.ValidateValue 0 2 1
A validation.*Validator.BuildViolation 0 2 1
A validation.*Validator.ValidateValidatable 0 2 1
A validation.*Validator.Validate 0 19 5
A validation.*Validator.ValidateBy 0 6 2
A validation.*Validator.StoreConstraint 0 8 2
1
package validation
2
3
import (
4
	"context"
5
	"time"
6
7
	"golang.org/x/text/language"
8
	"golang.org/x/text/message/catalog"
9
)
10
11
// Validator is the main validation service. It can be created by NewValidator constructor.
12
// Also, you can use singleton version from the package "github.com/muonsoft/validation/validator".
13
type Validator struct {
14
	scope Scope
15
}
16
17
// ValidatorOption is a base type for configuration options used to create a new instance of Validator.
18
type ValidatorOption func(validator *Validator) error
19
20
// NewValidator is a constructor for creating an instance of Validator.
21
// You can configure it by using validator options.
22
//
23
// Example
24
//  validator, err := validation.NewValidator(
25
//      validation.DefaultLanguage(language.Russian), // passing default language of translations
26
//      validation.Translations(russian.Messages), // setting up custom or built-in translations
27
//      validation.SetViolationFactory(userViolationFactory), // if you want to override creation of violations
28
//  )
29
//
30
//  // don't forget to check for errors
31
//  if err != nil {
32
//      fmt.Println(err)
33
//  }
34
func NewValidator(options ...ValidatorOption) (*Validator, error) {
35 1
	validator := &Validator{scope: newScope()}
36
37 1
	for _, setOption := range options {
38 1
		err := setOption(validator)
39 1
		if err != nil {
40
			return nil, err
41
		}
42
	}
43
44 1
	err := validator.scope.translator.init()
45 1
	if err != nil {
46 1
		return nil, err
47
	}
48
49 1
	return validator, nil
50
}
51
52
func newScopedValidator(scope Scope) *Validator {
53 1
	return &Validator{scope: scope}
54
}
55
56
// DefaultLanguage is used to set up the default language for translation of violation messages.
57
func DefaultLanguage(tag language.Tag) ValidatorOption {
58 1
	return func(validator *Validator) error {
59 1
		validator.scope.translator.defaultLanguage = tag
60 1
		return nil
61
	}
62
}
63
64
// Translations is used to load translation messages into the validator.
65
//
66
// By default, all violation messages are generated in the English language with pluralization capabilities.
67
// To use a custom language you have to load translations on validator initialization.
68
// Built-in translations are available in the sub-packages of the package "github.com/muonsoft/message/translations".
69
// The translation mechanism is provided by the "golang.org/x/text" package (be aware, it has no stable version yet).
70
//
71
// Example
72
//  // import "github.com/muonsoft/validation/message/translations/russian"
73
//
74
//  validator, err := validation.NewValidator(
75
//      validation.Translations(russian.Messages),
76
//  )
77
func Translations(messages map[language.Tag]map[string]catalog.Message) ValidatorOption {
78 1
	return func(validator *Validator) error {
79 1
		return validator.scope.translator.loadMessages(messages)
80
	}
81
}
82
83
// SetViolationFactory can be used to override the mechanism of violation creation.
84
func SetViolationFactory(factory ViolationFactory) ValidatorOption {
85 1
	return func(validator *Validator) error {
86 1
		validator.scope.violationFactory = factory
87
88 1
		return nil
89
	}
90
}
91
92
// Validate is the main validation method. It accepts validation arguments. Arguments can be
93
// used to tune up the validation process or to pass values of a specific type.
94
func (validator *Validator) Validate(arguments ...Argument) error {
95 1
	args := &Arguments{scope: validator.scope}
96 1
	for _, argument := range arguments {
97 1
		err := argument.set(args)
98 1
		if err != nil {
99 1
			return err
100
		}
101
	}
102
103 1
	violations := make(ViolationList, 0)
104 1
	for _, validate := range args.validators {
105 1
		vs, err := validate(args.scope)
106 1
		if err != nil {
107 1
			return err
108
		}
109 1
		violations = append(violations, vs...)
110
	}
111
112 1
	return violations.AsError()
113
}
114
115
// ValidateValue is an alias for validating a single value of any supported type.
116
func (validator *Validator) ValidateValue(value interface{}, options ...Option) error {
117 1
	return validator.Validate(Value(value, options...))
118
}
119
120
// ValidateBool is an alias for validating a single boolean value.
121
func (validator *Validator) ValidateBool(value *bool, options ...Option) error {
122 1
	return validator.Validate(Bool(value, options...))
123
}
124
125
// ValidateNumber is an alias for validating a single numeric value (integer or float).
126
func (validator *Validator) ValidateNumber(value interface{}, options ...Option) error {
127 1
	return validator.Validate(Number(value, options...))
128
}
129
130
// ValidateString is an alias for validating a single string value.
131
func (validator *Validator) ValidateString(value *string, options ...Option) error {
132 1
	return validator.Validate(String(value, options...))
133
}
134
135
// ValidateIterable is an alias for validating a single iterable value (an array, slice, or map).
136
func (validator *Validator) ValidateIterable(value interface{}, options ...Option) error {
137 1
	return validator.Validate(Iterable(value, options...))
138
}
139
140
// ValidateCountable is an alias for validating a single countable value (an array, slice, or map).
141
func (validator *Validator) ValidateCountable(count int, options ...Option) error {
142 1
	return validator.Validate(Countable(count, options...))
143
}
144
145
// ValidateTime is an alias for validating a single time value.
146
func (validator *Validator) ValidateTime(value *time.Time, options ...Option) error {
147 1
	return validator.Validate(Time(value, options...))
148
}
149
150
// ValidateEach is an alias for validating each value of an iterable (an array, slice, or map).
151
func (validator *Validator) ValidateEach(value interface{}, options ...Option) error {
152 1
	return validator.Validate(Each(value, options...))
153
}
154
155
// ValidateEachString is an alias for validating each value of a strings slice.
156
func (validator *Validator) ValidateEachString(values []string, options ...Option) error {
157 1
	return validator.Validate(EachString(values, options...))
158
}
159
160
// ValidateValidatable is an alias for validating value that implements the Validatable interface.
161
func (validator *Validator) ValidateValidatable(validatable Validatable, options ...Option) error {
162 1
	return validator.Validate(Valid(validatable, options...))
163
}
164
165
// WithContext method creates a new scoped validator with a given context. You can use this method to pass
166
// a context value to all used constraints.
167
//
168
// Example
169
//  err := validator.WithContext(request.Context()).Validate(
170
//      String(&s, it.IsNotBlank()), // now all called constraints will use passed context in their methods
171
//  )
172
func (validator *Validator) WithContext(ctx context.Context) *Validator {
173 1
	return newScopedValidator(validator.scope.withContext(ctx))
174
}
175
176
// WithLanguage method creates a new scoped validator with a given language tag. All created violations
177
// will be translated into this language.
178
//
179
// Example
180
//  err := validator.WithLanguage(language.Russian).Validate(
181
//      validation.ValidateString(&s, it.IsNotBlank()), // violation from this constraint will be translated
182
//  )
183
func (validator *Validator) WithLanguage(tag language.Tag) *Validator {
184 1
	return newScopedValidator(validator.scope.withLanguage(tag))
185
}
186
187
// AtProperty method creates a new scoped validator with injected property name element to scope property path.
188
func (validator *Validator) AtProperty(name string) *Validator {
189 1
	return newScopedValidator(validator.scope.atProperty(name))
190
}
191
192
// AtIndex method creates a new scoped validator with injected array index element to scope property path.
193
func (validator *Validator) AtIndex(index int) *Validator {
194 1
	return newScopedValidator(validator.scope.atIndex(index))
195
}
196
197
// BuildViolation can be used to build a custom violation on the client-side.
198
//
199
// Example
200
//  err := validator.BuildViolation("", "").
201
//      AddParameter("key", "value").
202
//      CreateViolation()
203
func (validator *Validator) BuildViolation(code, message string) *ViolationBuilder {
204 1
	return validator.scope.BuildViolation(code, message)
205
}
206
207
// StoreConstraint can be used to store a constraint in an internal validator store.
208
// It can later be used by the ValidateBy method. This can be useful for passing
209
// custom or prepared constraints to Validatable.
210
//
211
// If the constraint already exists, a ConstraintAlreadyStoredError is returned.
212
//
213
// Due to the fact that the store is a state element, it is strongly recommended
214
// to fill the store during application initialization to avoid unexpected problems.
215
//
216
// Example
217
//	err := validator.StoreConstraint("isTagExists", isTagExistsConstraint)
218
//	if err != nil {
219
//		log.Fatal(err)
220
//	}
221
//
222
//	s := "
223
//	err = validator.ValidateString(&s, validator.ValidateBy("isTagExists"))
224
func (validator *Validator) StoreConstraint(key string, constraint Constraint) error {
225 1
	if _, exists := validator.scope.constraints[key]; exists {
226 1
		return ConstraintAlreadyStoredError{Key: key}
227
	}
228
229 1
	validator.scope.constraints[key] = constraint
230
231 1
	return nil
232
}
233
234
// ValidateBy is used to get the constraint from the internal validator store.
235
// If the constraint does not exist, then the validator will
236
// return a ConstraintNotFoundError during the validation process.
237
// For storing a constraint you should use the StoreConstraint method.
238
func (validator *Validator) ValidateBy(constraintKey string) Constraint {
239 1
	if constraint, exists := validator.scope.constraints[constraintKey]; exists {
240 1
		return constraint
241
	}
242
243 1
	return notFoundConstraint{key: constraintKey}
244
}
245