Skip to content

Commit

Permalink
feat: Add WithNameFunc to Validator (#28)
Browse files Browse the repository at this point in the history
## Motivation

It's often the case that we wan't to dynamically construct the validated
entity's name.
It would be useful to provide a function that can do that for us.

## Release Notes

Added `Validator.WithNameFunc` function which can be used to dynamically
set the validator's name per-instance.
  • Loading branch information
nieomylnieja authored Oct 1, 2024
1 parent efe2932 commit 61d6ab0
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 3 deletions.
19 changes: 19 additions & 0 deletions pkg/govy/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ func ExampleValidator_WithName() {
// - always fails
}

// If statically defined name through [govy.Validator.WithName] is not enough,
// you can use [govy.Validator.WithNameFunc].
// The function receives the entity's instance you're validating and returns a string name.
func ExampleValidator_WithNameFunc() {
v := govy.New(
govy.For(func(t Teacher) string { return t.Name }).
Rules(govy.NewRule(func(name string) error { return fmt.Errorf("always fails") })),
).WithNameFunc(func(t Teacher) string { return "Teacher " + t.Name })

err := v.Validate(Teacher{Name: "John"})
if err != nil {
fmt.Println(err)
}

// Output:
// Validation for Teacher John has failed for the following properties:
// - always fails
}

// You can also add [govy.Validator] name during runtime,
// by calling [govy.ValidatorError.WithName] function on the returned error.
//
Expand Down
20 changes: 17 additions & 3 deletions pkg/govy/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,28 @@ func New[S any](props ...validationInterface[S]) Validator[S] {
// It serves as an aggregator for [PropertyRules].
// Typically, it represents a struct.
type Validator[S any] struct {
props []validationInterface[S]
name string
props []validationInterface[S]
name string
nameFunc func(S) string

predicateMatcher[S]
}

// WithName when a rule fails will pass the provided name to [ValidatorError.WithName].
func (v Validator[S]) WithName(name string) Validator[S] {
v.nameFunc = nil
v.name = name
return v
}

// WithNameFunc when a rule fails extracts name from provided function and passes it to [ValidatorError.WithName].
// The function receives validated entity's instance as an argument.
func (v Validator[S]) WithNameFunc(f func(s S) string) Validator[S] {
v.name = ""
v.nameFunc = f
return v
}

// When accepts predicates which will be evaluated BEFORE [Validator] validates ANY rules.
func (v Validator[S]) When(predicate Predicate[S], opts ...WhenOptions) Validator[S] {
v.predicateMatcher = v.when(predicate, opts...)
Expand Down Expand Up @@ -75,7 +85,11 @@ func (v Validator[S]) Validate(st S) error {
allErrors = append(allErrors, pErrs...)
}
if len(allErrors) != 0 {
return NewValidatorError(allErrors).WithName(v.name)
name := v.name
if v.nameFunc != nil {
name = v.nameFunc(st)
}
return NewValidatorError(allErrors).WithName(name)
}
return nil
}
Expand Down
14 changes: 14 additions & 0 deletions pkg/govy/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ func TestValidatorWithName(t *testing.T) {
- test`)
}

func TestValidatorWithNameFunc(t *testing.T) {
r := govy.New(
govy.For(func(m mockValidatorStruct) string { return "test" }).
WithName("test").
Rules(govy.NewRule(func(v string) error { return errors.New("test") })),
).WithNameFunc(func(m mockValidatorStruct) string { return "validator with field: " + m.Field })

err := r.Validate(mockValidatorStruct{Field: "FIELD"})
assert.Require(t, assert.Error(t, err))
assert.EqualError(t, err, `Validation for validator with field: FIELD has failed for the following properties:
- 'test' with value 'test':
- test`)
}

func TestValidatorInferName(t *testing.T) {
r := govy.New(
govy.For(func(m mockValidatorStruct) string { return "test" }).
Expand Down

0 comments on commit 61d6ab0

Please sign in to comment.