Passed
Push — main ( 08e96c...9065c5 )
by Igor
01:14 queued 11s
created

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