Test Failed
Push — main ( b26c62...faec64 )
by Igor
02:33
created

validation_test.*UniqueBrandConstraint.SetUp   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
nop 0
1
package validation_test
2
3
import (
4
	"context"
5
	"fmt"
6
7
	"github.com/muonsoft/validation"
8
	"github.com/muonsoft/validation/validator"
9
)
10
11
type Brand struct {
12
	Name string
13
}
14
15
type BrandRepository struct {
16
	brands []Brand
17
}
18
19
func (repository *BrandRepository) FindByName(ctx context.Context, name string) ([]Brand, error) {
20
	found := make([]Brand, 0)
21
22
	for _, brand := range repository.brands {
23
		if brand.Name == name {
24
			found = append(found, brand)
25
		}
26
	}
27
28
	return found, nil
29
}
30
31
// You can declare you own constraint interface to create custom constraints.
32
type BrandConstraint interface {
33
	ValidateBrand(brand *Brand, scope validation.Scope) error
34
}
35
36
// To create your own functional argument for validation simply create a function with
37
// a typed value and use the validation.NewArgument constructor.
38
func ValidBrand(brand *Brand, constraints ...BrandConstraint) validation.ValidatorArgument {
39
	return validation.NewArgument(func(scope validation.Scope) (*validation.ViolationList, error) {
40
		violations := validation.NewViolationList()
41
42
		for i := range constraints {
43
			err := violations.AppendFromError(constraints[i].ValidateBrand(brand, scope))
44
			if err != nil {
45
				return nil, err
46
			}
47
		}
48
49
		return violations, nil
50
	})
51
}
52
53
// UniqueBrandConstraint implements BrandConstraint.
54
type UniqueBrandConstraint struct {
55
	brands *BrandRepository
56
}
57
58
func (c *UniqueBrandConstraint) ValidateBrand(brand *Brand, scope validation.Scope) error {
59
	// usually, you should ignore empty values
60
	// to check for an empty value you should use it.NotBlankConstraint
61
	if brand == nil {
62
		return nil
63
	}
64
65
	// you can pass the context value from the scope
66
	brands, err := c.brands.FindByName(scope.Context(), brand.Name)
67
	// here you can return a service error so that the validation process
68
	// is stopped immediately
69
	if err != nil {
70
		return err
71
	}
72
	if len(brands) == 0 {
73
		return nil
74
	}
75
76
	// use the scope to build violation with translations
77
	return scope.
78
		BuildViolation("notUniqueBrand", `Brand with name "{{ name }}" already exists.`).
79
		// you can inject parameter value to the message here
80
		WithParameter("{{ name }}", brand.Name).
81
		Create()
82
}
83
84
func ExampleNewArgument_customArgumentConstraintValidator() {
85
	repository := &BrandRepository{brands: []Brand{{"Apple"}, {"Orange"}}}
86
	isEntityUnique := &UniqueBrandConstraint{brands: repository}
87
88
	brand := Brand{Name: "Apple"}
89
90
	err := validator.Validate(
91
		// you can pass here the context value to the validation scope
92
		context.WithValue(context.Background(), exampleKey, "value"),
93
		ValidBrand(&brand, isEntityUnique),
94
	)
95
96
	fmt.Println(err)
97
	// Output:
98
	// violation: Brand with name "Apple" already exists.
99
}
100