Test Failed
Push — main ( 19c488...747293 )
by Igor
02:02
created

validation_test.ExampleSetViolationFactory   A

Complexity

Conditions 4

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
dl 0
loc 27
rs 9.45
c 0
b 0
f 0
nop 0
1
package validation_test
2
3
import (
4
	"context"
5
	"encoding/json"
6
	"errors"
7
	"fmt"
8
	"log"
9
	"strings"
10
11
	"github.com/muonsoft/validation"
12
	"github.com/muonsoft/validation/it"
13
	"github.com/muonsoft/validation/message/translations"
14
	"golang.org/x/text/language"
15
)
16
17
// DomainError is the container that will pass the ID to DomainViolation.
18
type DomainError struct {
19
	ID      string // this ID will be passed into DomainViolation by DomainViolationFactory
20
	Message string
21
}
22
23
func (err *DomainError) Error() string {
24
	return err.Message
25
}
26
27
var ErrIsEmpty = &DomainError{ID: "IsEmpty", Message: "Value is empty."}
28
29
// DomainViolation is custom implementation of validation.Violation interface with domain
30
// data and custom marshaling to JSON.
31
type DomainViolation struct {
32
	id string // id passed from DomainError
33
34
	// required fields for implementing validation.Violation
35
	err             error
36
	message         string
37
	messageTemplate string
38
	parameters      []validation.TemplateParameter
39
	propertyPath    *validation.PropertyPath
40
}
41
42
func (v *DomainViolation) Unwrap() error                              { return v.err }
43
func (v *DomainViolation) Is(target error) bool                       { return errors.Is(v.err, target) }
44
func (v *DomainViolation) Error() string                              { return v.err.Error() }
45
func (v *DomainViolation) Message() string                            { return v.message }
46
func (v *DomainViolation) MessageTemplate() string                    { return v.messageTemplate }
47
func (v *DomainViolation) Parameters() []validation.TemplateParameter { return v.parameters }
48
func (v *DomainViolation) PropertyPath() *validation.PropertyPath     { return v.propertyPath }
49
50
// pathAsJSONPointer formats property path according to a JSON Pointer Syntax https://tools.ietf.org/html/rfc6901
51
func (v *DomainViolation) pathAsJSONPointer() string {
52
	var s strings.Builder
53
	for _, element := range v.propertyPath.Elements() {
54
		s.WriteRune('/')
55
		s.WriteString(element.String())
56
	}
57
	return s.String()
58
}
59
60
// MarshalJSON marshals violation data with id, message and path fields. Path is formatted
61
// according to JSON Pointer Syntax.
62
func (v *DomainViolation) MarshalJSON() ([]byte, error) {
63
	return json.Marshal(struct {
64
		ID           string `json:"id"`
65
		Message      string `json:"message"`
66
		PropertyPath string `json:"path"`
67
	}{
68
		ID:           v.id,
69
		Message:      v.message,
70
		PropertyPath: v.pathAsJSONPointer(),
71
	})
72
}
73
74
// DomainViolationFactory is custom implementation for validation.ViolationFactory.
75
type DomainViolationFactory struct {
76
	// reuse translations and templating from BuiltinViolationFactory
77
	factory *validation.BuiltinViolationFactory
78
}
79
80
func NewDomainViolationFactory() (*DomainViolationFactory, error) {
81
	translator, err := translations.NewTranslator()
82
	if err != nil {
83
		return nil, err
84
	}
85
86
	return &DomainViolationFactory{factory: validation.NewViolationFactory(translator)}, nil
87
}
88
89
func (factory *DomainViolationFactory) CreateViolation(err error, messageTemplate string, pluralCount int, parameters []validation.TemplateParameter, propertyPath *validation.PropertyPath, lang language.Tag) validation.Violation {
90
	// extracting error ID from err if it implements DomainError
91
	id := ""
92
	var domainErr *DomainError
93
	if errors.As(err, &domainErr) {
94
		id = domainErr.ID
95
	}
96
97
	violation := factory.factory.CreateViolation(err, messageTemplate, pluralCount, parameters, propertyPath, lang)
98
99
	return &DomainViolation{
100
		id:              id,
101
		err:             err,
102
		message:         violation.Message(),
103
		messageTemplate: violation.MessageTemplate(),
104
		parameters:      violation.Parameters(),
105
		propertyPath:    violation.PropertyPath(),
106
	}
107
}
108
109
func ExampleSetViolationFactory() {
110
	violationFactory, err := NewDomainViolationFactory()
111
	if err != nil {
112
		log.Fatalln(err)
113
	}
114
	validator, err := validation.NewValidator(validation.SetViolationFactory(violationFactory))
115
	if err != nil {
116
		log.Fatalln(err)
117
	}
118
119
	err = validator.At(
120
		// property path will be formatted according to JSON Pointer Syntax
121
		validation.PropertyName("properties"),
122
		validation.ArrayIndex(1),
123
		validation.PropertyName("key"),
124
	).ValidateString(
125
		context.Background(),
126
		"",
127
		// passing DomainError implementation via a constraint method
128
		it.IsNotBlank().WithError(ErrIsEmpty).WithMessage(ErrIsEmpty.Message),
129
	)
130
131
	marshaled, err := json.MarshalIndent(err, "", "\t")
132
	if err != nil {
133
		log.Fatalln(err)
134
	}
135
	fmt.Println(string(marshaled))
136
	// Output:
137
	// [
138
	//	{
139
	//		"id": "IsEmpty",
140
	//		"message": "Value is empty.",
141
	//		"path": "/properties/1/key"
142
	//	}
143
	// ]
144
}
145