Test Setup Failed
Pull Request — main (#71)
by Igor
02:10
created

validation.NewValidator   B

Complexity

Conditions 8

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 20
nop 1
dl 0
loc 31
ccs 16
cts 16
cp 1
crap 8
rs 7.3333
c 0
b 0
f 0
1
package validation
2
3
import (
4
	"context"
5
	"fmt"
6
	"time"
7
8
	"github.com/muonsoft/validation/message/translations"
9
	"golang.org/x/text/language"
10
	"golang.org/x/text/message/catalog"
11
)
12
13
// Validator is the main validation service. It can be created by NewValidator constructor.
14
// Also, you can use singleton version from the package "github.com/muonsoft/validation/validator".
15
type Validator struct {
16
	scope Scope
17
}
18
19
// Translator is used to translate violation messages. By default, validator uses an implementation from
20
// "github.com/muonsoft/validation/message/translations" package based on "golang.org/x/text" package.
21
// You can set up your own implementation by using SetTranslator option.
22
type Translator interface {
23
	Translate(tag language.Tag, message string, pluralCount int) string
24
}
25
26
// ValidatorOptions is a temporary structure for collecting functional options ValidatorOption.
27
type ValidatorOptions struct {
28
	translatorOptions []translations.TranslatorOption
29
	translator        Translator
30
	violationFactory  ViolationFactory
31
	constraints       map[string]Constraint
32
}
33
34
func newValidatorOptions() *ValidatorOptions {
35 1
	return &ValidatorOptions{
36
		constraints: map[string]Constraint{},
37
	}
38
}
39
40
// ValidatorOption is a base type for configuration options used to create a new instance of Validator.
41
type ValidatorOption func(options *ValidatorOptions) error
42
43
// NewValidator is a constructor for creating an instance of Validator.
44
// You can configure it by using validator options.
45
func NewValidator(options ...ValidatorOption) (*Validator, error) {
46 1
	var err error
47
48 1
	opts := newValidatorOptions()
49 1
	for _, setOption := range options {
50 1
		err = setOption(opts)
51 1
		if err != nil {
52 1
			return nil, err
53
		}
54
	}
55
56 1
	if opts.translator != nil && len(opts.translatorOptions) > 0 {
57 1
		return nil, errTranslatorOptionsDenied
58
	}
59 1
	if opts.translator == nil {
60 1
		opts.translator, err = translations.NewTranslator(opts.translatorOptions...)
61 1
		if err != nil {
62 1
			return nil, fmt.Errorf("failed to set up default translator: %w", err)
63
		}
64
	}
65 1
	if opts.violationFactory == nil {
66 1
		opts.violationFactory = newViolationFactory(opts.translator)
67
	}
68
69 1
	validator := &Validator{scope: newScope(
70
		opts.translator,
71
		opts.violationFactory,
72
		opts.constraints,
73
	)}
74
75 1
	return validator, nil
76
}
77
78
func newScopedValidator(scope Scope) *Validator {
79 1
	return &Validator{scope: scope}
80
}
81
82
// DefaultLanguage option is used to set up the default language for translation of violation messages.
83
func DefaultLanguage(tag language.Tag) ValidatorOption {
84 1
	return func(options *ValidatorOptions) error {
85 1
		options.translatorOptions = append(options.translatorOptions, translations.DefaultLanguage(tag))
86
87 1
		return nil
88
	}
89
}
90
91
// Translations option is used to load translation messages into the validator.
92
//
93
// By default, all violation messages are generated in the English language with pluralization capabilities.
94
// To use a custom language you have to load translations on validator initialization.
95
// Built-in translations are available in the sub-packages of the package "github.com/muonsoft/message/translations".
96
// The translation mechanism is provided by the "golang.org/x/text" package (be aware, it has no stable version yet).
97
func Translations(messages map[language.Tag]map[string]catalog.Message) ValidatorOption {
98 1
	return func(options *ValidatorOptions) error {
99 1
		options.translatorOptions = append(options.translatorOptions, translations.SetTranslations(messages))
100
101 1
		return nil
102
	}
103
}
104
105
// SetTranslator option is used to set up the custom implementation of message violation translator.
106
func SetTranslator(translator Translator) ValidatorOption {
107 1
	return func(options *ValidatorOptions) error {
108 1
		options.translator = translator
109
110 1
		return nil
111
	}
112
}
113
114
// SetViolationFactory option can be used to override the mechanism of violation creation.
115
func SetViolationFactory(factory ViolationFactory) ValidatorOption {
116 1
	return func(options *ValidatorOptions) error {
117 1
		options.violationFactory = factory
118
119 1
		return nil
120
	}
121
}
122
123
// StoredConstraint option can be used to store a constraint in an internal validator store.
124
// It can later be used by the validator.ValidateBy method. This can be useful for passing
125
// custom or prepared constraints to Validatable.
126
//
127
// If the constraint already exists, a ConstraintAlreadyStoredError will be returned.
128
func StoredConstraint(key string, constraint Constraint) ValidatorOption {
129 1
	return func(options *ValidatorOptions) error {
130 1
		if _, exists := options.constraints[key]; exists {
131 1
			return ConstraintAlreadyStoredError{Key: key}
132
		}
133
134 1
		options.constraints[key] = constraint
135
136 1
		return nil
137
	}
138
}
139
140
// Validate is the main validation method. It accepts validation arguments. executionContext can be
141
// used to tune up the validation process or to pass values of a specific type.
142
func (validator *Validator) Validate(ctx context.Context, arguments ...Argument) error {
143 1
	execContext := &executionContext{scope: validator.scope.withContext(ctx)}
144 1
	for _, argument := range arguments {
145 1
		argument.setUp(execContext)
146 1
	}
147 1
148
	violations := &ViolationList{}
149
	for _, validate := range execContext.validators {
150
		vs, err := validate(execContext.scope)
151 1
		if err != nil {
152 1
			return err
153 1
		}
154 1
		violations.Join(vs)
155 1
	}
156
157 1
	return violations.AsError()
158
}
159
160 1
// ValidateBool is an alias for validating a single boolean value.
161
func (validator *Validator) ValidateBool(ctx context.Context, value bool, constraints ...BoolConstraint) error {
162
	return validator.Validate(ctx, Bool(value, constraints...))
163
}
164
165 1
// ValidateInt is an alias for validating a single integer value.
166
func (validator *Validator) ValidateInt(ctx context.Context, value int, constraints ...NumberConstraint[int]) error {
167
	return validator.Validate(ctx, Number(value, constraints...))
168
}
169
170 1
// ValidateFloat is an alias for validating a single float value.
171
func (validator *Validator) ValidateFloat(ctx context.Context, value float64, constraints ...NumberConstraint[float64]) error {
172
	return validator.Validate(ctx, Number(value, constraints...))
173
}
174
175 1
// ValidateString is an alias for validating a single string value.
176
func (validator *Validator) ValidateString(ctx context.Context, value string, constraints ...StringConstraint) error {
177
	return validator.Validate(ctx, String(value, constraints...))
178
}
179
180 1
// ValidateStrings is an alias for validating slice of strings.
181
func (validator *Validator) ValidateStrings(ctx context.Context, values []string, constraints ...ComparablesConstraint[string]) error {
182
	return validator.Validate(ctx, Comparables(values, constraints...))
183
}
184
185
// ValidateCountable is an alias for validating a single countable value (an array, slice, or map).
186
func (validator *Validator) ValidateCountable(ctx context.Context, count int, constraints ...CountableConstraint) error {
187
	return validator.Validate(ctx, Countable(count, constraints...))
188
}
189
190 1
// ValidateTime is an alias for validating a single time value.
191
func (validator *Validator) ValidateTime(ctx context.Context, value time.Time, constraints ...TimeConstraint) error {
192
	return validator.Validate(ctx, Time(value, constraints...))
193
}
194
195 1
// ValidateEachString is an alias for validating each value of a strings slice.
196
func (validator *Validator) ValidateEachString(ctx context.Context, values []string, constraints ...StringConstraint) error {
197
	return validator.Validate(ctx, EachString(values, constraints...))
198
}
199
200 1
// ValidateIt is an alias for validating value that implements the Validatable interface.
201
func (validator *Validator) ValidateIt(ctx context.Context, validatable Validatable) error {
202
	return validator.Validate(ctx, Valid(validatable))
203
}
204
205 1
// ValidateBy is used to get the constraint from the internal validator store.
206
// If the constraint does not exist, then the validator will
207
// return a ConstraintNotFoundError during the validation process.
208
// For storing a constraint you should use the StoredConstraint option.
209
func (validator *Validator) ValidateBy(constraintKey string) Constraint {
210 1
	if constraint, exists := validator.scope.constraints[constraintKey]; exists {
211
		return constraint
212
	}
213
214
	return notFoundConstraint{key: constraintKey}
215 1
}
216
217
// WithGroups is used to execute conditional validation based on validation groups. It creates
218
// a new scoped validation with a given set of groups.
219
//
220
// By default, when validating an object all constraints of it will be checked whether or not
221
// they pass. In some cases, however, you will need to validate an object against
222
// only some specific group of constraints. To do this, you can organize each constraint
223 1
// into one or more validation groups and then apply validation against one group of constraints.
224 1
//
225
// Validation groups are working together only with validation groups passed
226
// to a constraint by WhenGroups() method. This method is implemented in all built-in constraints.
227 1
// If you want to use validation groups for your own constraints do not forget to implement
228
// this method in your constraint.
229
//
230
// Be careful, empty groups are considered as the default group. Its value is equal to the DefaultGroup ("default").
231
func (validator *Validator) WithGroups(groups ...string) *Validator {
232
	return newScopedValidator(validator.scope.withGroups(groups...))
233
}
234
235
// WithLanguage method creates a new scoped validator with a given language tag. All created violations
236
// will be translated into this language.
237
func (validator *Validator) WithLanguage(tag language.Tag) *Validator {
238
	return newScopedValidator(validator.scope.withLanguage(tag))
239
}
240
241
// AtProperty method creates a new scoped validator with injected property name element to scope property path.
242
func (validator *Validator) AtProperty(name string) *Validator {
243
	return newScopedValidator(validator.scope.AtProperty(name))
244
}
245 1
246
// AtIndex method creates a new scoped validator with injected array index element to scope property path.
247
func (validator *Validator) AtIndex(index int) *Validator {
248
	return newScopedValidator(validator.scope.AtIndex(index))
249
}
250
251 1
// BuildViolation can be used to build a custom violation on the client-side.
252
func (validator *Validator) BuildViolation(ctx context.Context, code, message string) *ViolationBuilder {
253
	return validator.scope.withContext(ctx).BuildViolation(code, message)
254
}
255