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

validator.go   B

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Test Coverage

Coverage 98.48%

Importance

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