Passed
Push — main ( 8592e5...add42e )
by Rushan
55s queued 12s
created

validation.*Validator.Context   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
crap 1
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
// Context returns context from current validation scope. By default it returns context.Background.
166
// You can create scoped validator with context by calling WithContext method.
167
func (validator *Validator) Context() context.Context {
168 1
	return validator.scope.context
169
}
170
171
// WithContext method creates a new scoped validator with a given context. You can use this method to pass
172
// a context value to all used constraints.
173
//
174
// Example
175
//  err := validator.WithContext(request.Context()).Validate(
176
//      String(&s, it.IsNotBlank()), // now all called constraints will use passed context in their methods
177
//  )
178
func (validator *Validator) WithContext(ctx context.Context) *Validator {
179 1
	return newScopedValidator(validator.scope.withContext(ctx))
180
}
181
182
// WithLanguage method creates a new scoped validator with a given language tag. All created violations
183
// will be translated into this language.
184
//
185
// Example
186
//  err := validator.WithLanguage(language.Russian).Validate(
187
//      validation.ValidateString(&s, it.IsNotBlank()), // violation from this constraint will be translated
188
//  )
189
func (validator *Validator) WithLanguage(tag language.Tag) *Validator {
190 1
	return newScopedValidator(validator.scope.withLanguage(tag))
191
}
192
193
// AtProperty method creates a new scoped validator with injected property name element to scope property path.
194
func (validator *Validator) AtProperty(name string) *Validator {
195 1
	return newScopedValidator(validator.scope.atProperty(name))
196
}
197
198
// AtIndex method creates a new scoped validator with injected array index element to scope property path.
199
func (validator *Validator) AtIndex(index int) *Validator {
200 1
	return newScopedValidator(validator.scope.atIndex(index))
201
}
202
203
// BuildViolation can be used to build a custom violation on the client-side.
204
//
205
// Example
206
//  err := validator.BuildViolation("", "").
207
//      AddParameter("key", "value").
208
//      CreateViolation()
209
func (validator *Validator) BuildViolation(code, message string) *ViolationBuilder {
210 1
	return validator.scope.BuildViolation(code, message)
211
}
212
213
// StoreConstraint can be used to store a constraint in an internal validator store.
214
// It can later be used by the ValidateBy method. This can be useful for passing
215
// custom or prepared constraints to Validatable.
216
//
217
// If the constraint already exists, a ConstraintAlreadyStoredError is returned.
218
//
219
// Due to the fact that the store is a state element, it is strongly recommended
220
// to fill the store during application initialization to avoid unexpected problems.
221
//
222
// Example
223
//	err := validator.StoreConstraint("isTagExists", isTagExistsConstraint)
224
//	if err != nil {
225
//		log.Fatal(err)
226
//	}
227
//
228
//	s := "
229
//	err = validator.ValidateString(&s, validator.ValidateBy("isTagExists"))
230
func (validator *Validator) StoreConstraint(key string, constraint Constraint) error {
231 1
	if _, exists := validator.scope.constraints[key]; exists {
232 1
		return ConstraintAlreadyStoredError{Key: key}
233
	}
234
235 1
	validator.scope.constraints[key] = constraint
236
237 1
	return nil
238
}
239
240
// ValidateBy is used to get the constraint from the internal validator store.
241
// If the constraint does not exist, then the validator will
242
// return a ConstraintNotFoundError during the validation process.
243
// For storing a constraint you should use the StoreConstraint method.
244
func (validator *Validator) ValidateBy(constraintKey string) Constraint {
245 1
	if constraint, exists := validator.scope.constraints[constraintKey]; exists {
246 1
		return constraint
247
	}
248
249 1
	return notFoundConstraint{key: constraintKey}
250
}
251