example_partial_entity_validation_test.go   A
last analyzed

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 85
dl 0
loc 150
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A validation_test.File.Validate 0 4 1
A validation_test.ValidFile 0 12 4
A validation_test.FileHasAllowedExtension 0 2 1
A validation_test.FileHasAllowedSize 0 2 1
A validation_test.ExampleValidator_Validate_partialEntityValidation 0 43 4
A validation_test.AllowedFileExtensionConstraint.ValidateFile 0 12 2
A validation_test.AllowedFileSizeConstraint.ValidateFile 0 13 2
1
package validation_test
2
3
import (
4
	"bytes"
5
	"context"
6
	"fmt"
7
	"path/filepath"
8
	"strings"
9
10
	"github.com/muonsoft/validation"
11
	"github.com/muonsoft/validation/it"
12
	"github.com/muonsoft/validation/validator"
13
)
14
15
type File struct {
16
	Name string
17
	Data []byte
18
}
19
20
// This validation will always check that file is valid.
21
// Partial validation will be applied by AllowedFileExtensionConstraint
22
// and AllowedFileSizeConstraint.
23
func (f File) Validate(ctx context.Context, validator *validation.Validator) error {
24
	return validator.Validate(
25
		ctx,
26
		validation.StringProperty("name", f.Name, it.HasLengthBetween(5, 50)),
27
	)
28
}
29
30
type FileUploadRequest struct {
31
	Section string
32
	File    *File
33
}
34
35
type FileConstraint interface {
36
	ValidateFile(ctx context.Context, validator *validation.Validator, file *File) error
37
}
38
39
func ValidFile(file *File, constraints ...FileConstraint) validation.ValidatorArgument {
40
	return validation.NewArgument(func(ctx context.Context, validator *validation.Validator) (*validation.ViolationList, error) {
41
		violations := validation.NewViolationList()
42
43
		for _, constraint := range constraints {
44
			err := violations.AppendFromError(constraint.ValidateFile(ctx, validator, file))
45
			if err != nil {
46
				return nil, err
47
			}
48
		}
49
50
		return violations, nil
51
	})
52
}
53
54
// AllowedFileExtensionConstraint used to check that file has one of allowed extensions.
55
// This constraint can be used for partial validation.
56
type AllowedFileExtensionConstraint struct {
57
	extensions []string
58
}
59
60
func FileHasAllowedExtension(extensions ...string) AllowedFileExtensionConstraint {
61
	return AllowedFileExtensionConstraint{extensions: extensions}
62
}
63
64
func (c AllowedFileExtensionConstraint) ValidateFile(ctx context.Context, validator *validation.Validator, file *File) error {
65
	if file == nil {
66
		return nil
67
	}
68
69
	extension := strings.ReplaceAll(filepath.Ext(file.Name), ".", "")
70
71
	return validator.AtProperty("name").Validate(
72
		ctx,
73
		validation.Comparable[string](
74
			extension,
75
			it.IsOneOf(c.extensions...).WithMessage("Not allowed extension. Must be one of: {{ choices }}."),
76
		),
77
	)
78
}
79
80
// AllowedFileSizeConstraint used to check that file has limited size.
81
// This constraint can be used for partial validation.
82
type AllowedFileSizeConstraint struct {
83
	minSize int
84
	maxSize int
85
}
86
87
func FileHasAllowedSize(min, max int) AllowedFileSizeConstraint {
88
	return AllowedFileSizeConstraint{minSize: min, maxSize: max}
89
}
90
91
func (c AllowedFileSizeConstraint) ValidateFile(ctx context.Context, validator *validation.Validator, file *File) error {
92
	if file == nil {
93
		return nil
94
	}
95
96
	size := len(file.Data)
97
98
	return validator.Validate(
99
		ctx,
100
		validation.Number[int](
101
			size,
102
			it.IsGreaterThan(c.minSize).WithMessage("File size is too small."),
103
			it.IsLessThan(c.maxSize).WithMessage("File size is too large."),
104
		),
105
	)
106
}
107
108
func ExampleValidator_Validate_partialEntityValidation() {
109
	// this constraints will be applied to all files uploaded as avatars
110
	avatarConstraints := []FileConstraint{
111
		FileHasAllowedExtension("jpeg", "jpg", "gif"),
112
		FileHasAllowedSize(100, 1000),
113
	}
114
	// this constraints will be applied to all files uploaded as documents
115
	documentConstraints := []FileConstraint{
116
		FileHasAllowedExtension("doc", "pdf", "txt"),
117
		FileHasAllowedSize(1000, 100000),
118
	}
119
120
	requests := []FileUploadRequest{
121
		{
122
			Section: "avatars",
123
			File:    &File{Name: "avatar.png", Data: bytes.Repeat([]byte{0}, 99)},
124
		},
125
		{
126
			Section: "documents",
127
			File:    &File{Name: "sheet.xls", Data: bytes.Repeat([]byte{0}, 100001)},
128
		},
129
	}
130
131
	for _, request := range requests {
132
		switch request.Section {
133
		case "avatars":
134
			err := validator.Validate(
135
				context.Background(),
136
				// common validation of validatable
137
				validation.Valid(request.File),
138
				// specific validation for file storage section
139
				ValidFile(request.File, avatarConstraints...),
140
			)
141
			fmt.Println(err)
142
		case "documents":
143
			err := validator.Validate(
144
				context.Background(),
145
				// common validation of validatable
146
				validation.Valid(request.File),
147
				// specific validation for file storage section
148
				ValidFile(request.File, documentConstraints...),
149
			)
150
			fmt.Println(err)
151
		}
152
	}
153
154
	// Output:
155
	// violations: #0 at "name": "Not allowed extension. Must be one of: jpeg, jpg, gif."; #1: "File size is too small."
156
	// violations: #0 at "name": "Not allowed extension. Must be one of: doc, pdf, txt."; #1: "File size is too large."
157
}
158