Passed
Pull Request — main (#57)
by Igor
01:50
created

validation.SetViolationFactory   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
nop 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 1
			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
// StoredConstraint option can be used to store a constraint in an internal validator store.
93
// It can later be used by the validator.ValidateBy method. This can be useful for passing
94
// custom or prepared constraints to Validatable.
95
//
96
// If the constraint already exists, a ConstraintAlreadyStoredError will be returned.
97
//
98
// Example
99
//	validator, err := validation.NewValidator(
100
//		validation.StoredConstraint("isTagExists", isTagExistsConstraint)
101
//	)
102
//	if err != nil {
103
//		log.Fatal(err)
104
//	}
105
//
106
//	s := "
107
//	err = validator.ValidateString(&s, validator.ValidateBy("isTagExists"))
108
func StoredConstraint(key string, constraint Constraint) ValidatorOption {
109 1
	return func(validator *Validator) error {
110 1
		if _, exists := validator.scope.constraints[key]; exists {
111 1
			return ConstraintAlreadyStoredError{Key: key}
112
		}
113
114 1
		validator.scope.constraints[key] = constraint
115
116 1
		return nil
117
	}
118
}
119
120
// Validate is the main validation method. It accepts validation arguments. Arguments can be
121
// used to tune up the validation process or to pass values of a specific type.
122
func (validator *Validator) Validate(ctx context.Context, arguments ...Argument) error {
123 1
	args := &Arguments{scope: validator.scope.withContext(ctx)}
124 1
	for _, argument := range arguments {
125 1
		err := argument.set(args)
126 1
		if err != nil {
127 1
			return err
128
		}
129
	}
130
131 1
	violations := &ViolationList{}
132 1
	for _, validate := range args.validators {
133 1
		vs, err := validate(args.scope)
134 1
		if err != nil {
135 1
			return err
136
		}
137 1
		violations.Join(vs)
138
	}
139
140 1
	return violations.AsError()
141
}
142
143
// ValidateValue is an alias for validating a single value of any supported type.
144
func (validator *Validator) ValidateValue(ctx context.Context, value interface{}, options ...Option) error {
145 1
	return validator.Validate(ctx, Value(value, options...))
146
}
147
148
// ValidateBool is an alias for validating a single boolean value.
149
func (validator *Validator) ValidateBool(ctx context.Context, value *bool, options ...Option) error {
150 1
	return validator.Validate(ctx, Bool(value, options...))
151
}
152
153
// ValidateNumber is an alias for validating a single numeric value (integer or float).
154
func (validator *Validator) ValidateNumber(ctx context.Context, value interface{}, options ...Option) error {
155 1
	return validator.Validate(ctx, Number(value, options...))
156
}
157
158
// ValidateString is an alias for validating a single string value.
159
func (validator *Validator) ValidateString(ctx context.Context, value *string, options ...Option) error {
160 1
	return validator.Validate(ctx, String(value, options...))
161
}
162
163
// ValidateStrings is an alias for validating slice of strings.
164
func (validator *Validator) ValidateStrings(ctx context.Context, values []string, options ...Option) error {
165 1
	return validator.Validate(ctx, Strings(values, options...))
166
}
167
168
// ValidateIterable is an alias for validating a single iterable value (an array, slice, or map).
169
func (validator *Validator) ValidateIterable(ctx context.Context, value interface{}, options ...Option) error {
170 1
	return validator.Validate(ctx, Iterable(value, options...))
171
}
172
173
// ValidateCountable is an alias for validating a single countable value (an array, slice, or map).
174
func (validator *Validator) ValidateCountable(ctx context.Context, count int, options ...Option) error {
175 1
	return validator.Validate(ctx, Countable(count, options...))
176
}
177
178
// ValidateTime is an alias for validating a single time value.
179
func (validator *Validator) ValidateTime(ctx context.Context, value *time.Time, options ...Option) error {
180 1
	return validator.Validate(ctx, Time(value, options...))
181
}
182
183
// ValidateEach is an alias for validating each value of an iterable (an array, slice, or map).
184
func (validator *Validator) ValidateEach(ctx context.Context, value interface{}, options ...Option) error {
185 1
	return validator.Validate(ctx, Each(value, options...))
186
}
187
188
// ValidateEachString is an alias for validating each value of a strings slice.
189
func (validator *Validator) ValidateEachString(ctx context.Context, values []string, options ...Option) error {
190 1
	return validator.Validate(ctx, EachString(values, options...))
191
}
192
193
// ValidateValidatable is an alias for validating value that implements the Validatable interface.
194
func (validator *Validator) ValidateValidatable(ctx context.Context, validatable Validatable, options ...Option) error {
195 1
	return validator.Validate(ctx, Valid(validatable, options...))
196
}
197
198
// WithLanguage method creates a new scoped validator with a given language tag. All created violations
199
// will be translated into this language.
200
//
201
// Example
202
//  err := validator.WithLanguage(language.Russian).Validate(
203
//      validation.ValidateString(&s, it.IsNotBlank()), // violation from this constraint will be translated
204
//  )
205
func (validator *Validator) WithLanguage(tag language.Tag) *Validator {
206 1
	return newScopedValidator(validator.scope.withLanguage(tag))
207
}
208
209
// AtProperty method creates a new scoped validator with injected property name element to scope property path.
210
func (validator *Validator) AtProperty(name string) *Validator {
211 1
	return newScopedValidator(validator.scope.AtProperty(name))
212
}
213
214
// AtIndex method creates a new scoped validator with injected array index element to scope property path.
215
func (validator *Validator) AtIndex(index int) *Validator {
216 1
	return newScopedValidator(validator.scope.AtIndex(index))
217
}
218
219
// BuildViolation can be used to build a custom violation on the client-side.
220
func (validator *Validator) BuildViolation(ctx context.Context, code, message string) *ViolationBuilder {
221 1
	return validator.scope.withContext(ctx).BuildViolation(code, message)
222
}
223
224
// ValidateBy is used to get the constraint from the internal validator store.
225
// If the constraint does not exist, then the validator will
226
// return a ConstraintNotFoundError during the validation process.
227
// For storing a constraint you should use the StoreConstraint method.
228
func (validator *Validator) ValidateBy(constraintKey string) Constraint {
229 1
	if constraint, exists := validator.scope.constraints[constraintKey]; exists {
230 1
		return constraint
231
	}
232
233 1
	return notFoundConstraint{key: constraintKey}
234
}
235