Passed
Push — main ( b66d68...556c6a )
by Rushan
02:08 queued 12s
created

validation_test.StockItem.Validate   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
nop 1
1
package validation_test
2
3
import (
4
	"context"
5
	"errors"
6
	"fmt"
7
	"log"
8
9
	"github.com/muonsoft/validation"
10
	"github.com/muonsoft/validation/it"
11
)
12
13
type contextKey string
14
15
const exampleKey contextKey = "exampleKey"
16
17
type TagStorage struct {
18
	// this might be stored in the database
19
	tags []string
20
}
21
22
func (storage *TagStorage) FindByName(ctx context.Context, name string) ([]string, error) {
23
	contextValue, ok := ctx.Value(exampleKey).(string)
24
	if !ok {
25
		return nil, errors.New("context value missing")
26
	}
27
	if contextValue != "value" {
28
		return nil, errors.New("invalid context value")
29
	}
30
31
	found := make([]string, 0)
32
33
	for _, tag := range storage.tags {
34
		if tag == name {
35
			found = append(found, tag)
36
		}
37
	}
38
39
	return found, nil
40
}
41
42
type ExistingTagConstraint struct {
43
	storage *TagStorage
44
}
45
46
func (c *ExistingTagConstraint) SetUp() error {
47
	return nil
48
}
49
50
func (c *ExistingTagConstraint) Name() string {
51
	return "ExistingTagConstraint"
52
}
53
54
func (c *ExistingTagConstraint) ValidateString(value *string, scope validation.Scope) error {
55
	// usually, you should ignore empty values
56
	// to check for an empty value you should use it.NotBlankConstraint
57
	if value == nil || *value == "" {
58
		return nil
59
	}
60
61
	// you can pass the context value from the scope
62
	entities, err := c.storage.FindByName(scope.Context(), *value)
63
	// here you can return a service error so that the validation process
64
	// is stopped immediately
65
	if err != nil {
66
		return err
67
	}
68
	if len(entities) > 0 {
69
		return nil
70
	}
71
72
	// use the scope to build violation with translations
73
	return scope.
74
		BuildViolation("unknownTag", `Tag "{{ value }}" does not exist.`).
75
		// you can inject parameter value to the message here
76
		AddParameter("{{ value }}", *value).
77
		CreateViolation()
78
}
79
80
type StockItem struct {
81
	Name string
82
	Tags []string
83
}
84
85
func (s StockItem) Validate(validator *validation.Validator) error {
86
	return validator.Validate(
87
		validation.StringProperty("name", &s.Name, it.IsNotBlank(), it.HasMaxLength(20)),
88
		validation.EachStringProperty("tags", s.Tags, validator.ValidateBy("isTagExists")),
89
	)
90
}
91
92
func ExampleValidator_ValidateBy_customServiceConstraint() {
93
	validator, err := validation.NewValidator()
94
	if err != nil {
95
		log.Fatal(err)
96
	}
97
98
	storage := &TagStorage{tags: []string{"movie", "book"}}
99
	isTagExists := &ExistingTagConstraint{storage: storage}
100
	// custom constraint can be stored in the validator's internal store
101
	// and can be used later by calling the validator.ValidateBy method
102
	err = validator.StoreConstraint("isTagExists", isTagExists)
103
	if err != nil {
104
		log.Fatal(err)
105
	}
106
107
	item := StockItem{
108
		Name: "War and peace",
109
		Tags: []string{"book", "camera"},
110
	}
111
	ctx := context.WithValue(context.Background(), exampleKey, "value")
112
113
	err = validator.Validate(
114
		// you can pass here the context value to the validation scope
115
		validation.Context(ctx),
116
		validation.Valid(item),
117
	)
118
119
	violations := err.(validation.ViolationList)
120
	for _, violation := range violations {
121
		fmt.Println(violation.Error())
122
	}
123
	// Output:
124
	// violation at 'tags[1]': Tag "camera" does not exist.
125
}
126