Passed
Push — main ( f2c4e5...99f745 )
by Igor
57s queued 11s
created

validation.StoredConstraint   A

Complexity

Conditions 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
ccs 5
cts 5
cp 1
crap 3
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. Arguments 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
	args := &Arguments{scope: validator.scope.withContext(ctx)}
144 1
	for _, argument := range arguments {
145 1
		err := argument.set(args)
146 1
		if err != nil {
147 1
			return err
148
		}
149
	}
150
151 1
	violations := &ViolationList{}
152 1
	for _, validate := range args.validators {
153 1
		vs, err := validate(args.scope)
154 1
		if err != nil {
155 1
			return err
156
		}
157 1
		violations.Join(vs)
158
	}
159
160 1
	return violations.AsError()
161
}
162
163
// ValidateValue is an alias for validating a single value of any supported type.
164
func (validator *Validator) ValidateValue(ctx context.Context, value interface{}, options ...Option) error {
165 1
	return validator.Validate(ctx, Value(value, options...))
166
}
167
168
// ValidateBool is an alias for validating a single boolean value.
169
func (validator *Validator) ValidateBool(ctx context.Context, value bool, options ...Option) error {
170 1
	return validator.Validate(ctx, Bool(value, options...))
171
}
172
173
// ValidateNumber is an alias for validating a single numeric value (integer or float).
174
func (validator *Validator) ValidateNumber(ctx context.Context, value interface{}, options ...Option) error {
175 1
	return validator.Validate(ctx, Number(value, options...))
176
}
177
178
// ValidateString is an alias for validating a single string value.
179
func (validator *Validator) ValidateString(ctx context.Context, value string, options ...Option) error {
180 1
	return validator.Validate(ctx, String(value, options...))
181
}
182
183
// ValidateStrings is an alias for validating slice of strings.
184
func (validator *Validator) ValidateStrings(ctx context.Context, values []string, options ...Option) error {
185
	return validator.Validate(ctx, Strings(values, options...))
186
}
187
188
// ValidateIterable is an alias for validating a single iterable value (an array, slice, or map).
189
func (validator *Validator) ValidateIterable(ctx context.Context, value interface{}, options ...Option) error {
190 1
	return validator.Validate(ctx, Iterable(value, options...))
191
}
192
193
// ValidateCountable is an alias for validating a single countable value (an array, slice, or map).
194
func (validator *Validator) ValidateCountable(ctx context.Context, count int, options ...Option) error {
195 1
	return validator.Validate(ctx, Countable(count, options...))
196
}
197
198
// ValidateTime is an alias for validating a single time value.
199
func (validator *Validator) ValidateTime(ctx context.Context, value time.Time, options ...Option) error {
200 1
	return validator.Validate(ctx, Time(value, options...))
201
}
202
203
// ValidateEach is an alias for validating each value of an iterable (an array, slice, or map).
204
func (validator *Validator) ValidateEach(ctx context.Context, value interface{}, options ...Option) error {
205 1
	return validator.Validate(ctx, Each(value, options...))
206
}
207
208
// ValidateEachString is an alias for validating each value of a strings slice.
209
func (validator *Validator) ValidateEachString(ctx context.Context, values []string, options ...Option) error {
210 1
	return validator.Validate(ctx, EachString(values, options...))
211
}
212
213
// ValidateValidatable is an alias for validating value that implements the Validatable interface.
214
func (validator *Validator) ValidateValidatable(ctx context.Context, validatable Validatable, options ...Option) error {
215 1
	return validator.Validate(ctx, Valid(validatable, options...))
216
}
217
218
// ValidateBy is used to get the constraint from the internal validator store.
219
// If the constraint does not exist, then the validator will
220
// return a ConstraintNotFoundError during the validation process.
221
// For storing a constraint you should use the StoredConstraint option.
222
func (validator *Validator) ValidateBy(constraintKey string) Constraint {
223 1
	if constraint, exists := validator.scope.constraints[constraintKey]; exists {
224 1
		return constraint
225
	}
226
227 1
	return notFoundConstraint{key: constraintKey}
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 1
	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 1
	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 1
	return newScopedValidator(validator.scope.AtIndex(index))
244
}
245
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 1
	return validator.scope.withContext(ctx).BuildViolation(code, message)
249
}
250