Passed
Pull Request — main (#48)
by Igor
02:21
created

validation_test.FileArgument   A

Complexity

Conditions 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nop 2
dl 0
loc 15
rs 9.3333
c 0
b 0
f 0
1
package validation_test
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"path/filepath"
7
	"strings"
8
9
	"github.com/muonsoft/validation"
10
	"github.com/muonsoft/validation/it"
11
	"github.com/muonsoft/validation/validator"
12
)
13
14
type File struct {
15
	Name string
16
	Data []byte
17
}
18
19
// This validation will always check that file is valid.
20
// Partial validation will be applied by AllowedFileExtensionConstraint
21
// and AllowedFileSizeConstraint.
22
func (f File) Validate(validator *validation.Validator) error {
23
	return validator.Validate(
24
		validation.StringProperty("name", &f.Name, it.HasLengthBetween(5, 50)),
25
	)
26
}
27
28
type FileUploadRequest struct {
29
	Section string
30
	File    *File
31
}
32
33
type FileConstraint interface {
34
	validation.Constraint
35
	ValidateFile(file *File, scope validation.Scope) error
36
}
37
38
func FileArgument(file *File, options ...validation.Option) validation.Argument {
39
	return validation.NewArgument(options, func(constraint validation.Constraint, scope validation.Scope) error {
40
		if c, ok := constraint.(FileConstraint); ok {
41
			return c.ValidateFile(file, scope)
42
		}
43
		// If you want to use built-in constraints for checking for nil or empty values
44
		// such as it.IsNil() or it.IsBlank().
45
		if c, ok := constraint.(validation.NilConstraint); ok {
46
			if file == nil {
47
				return c.ValidateNil(scope)
48
			}
49
			return nil
50
		}
51
52
		return validation.NewInapplicableConstraintError(constraint, "File")
53
	})
54
}
55
56
// AllowedFileExtensionConstraint used to check that file has one of allowed extensions.
57
// This constraint can be used for partial validation.
58
type AllowedFileExtensionConstraint struct {
59
	extensions []string
60
}
61
62
func FileHasAllowedExtension(extensions ...string) AllowedFileExtensionConstraint {
63
	return AllowedFileExtensionConstraint{extensions: extensions}
64
}
65
66
func (c AllowedFileExtensionConstraint) SetUp() error {
67
	return nil
68
}
69
70
func (c AllowedFileExtensionConstraint) Name() string {
71
	return "AllowedFileExtensionConstraint"
72
}
73
74
func (c AllowedFileExtensionConstraint) ValidateFile(file *File, scope validation.Scope) error {
75
	if file == nil {
76
		return nil
77
	}
78
79
	extension := strings.ReplaceAll(filepath.Ext(file.Name), ".", "")
80
81
	return scope.Validator().AtProperty("name").ValidateString(
82
		&extension,
83
		it.IsOneOfStrings(c.extensions...).Message("Not allowed extension. Must be one of: {{ choices }}."),
84
	)
85
}
86
87
// AllowedFileSizeConstraint used to check that file has limited size.
88
// This constraint can be used for partial validation.
89
type AllowedFileSizeConstraint struct {
90
	minSize int64
91
	maxSize int64
92
}
93
94
func FileHasAllowedSize(min, max int64) AllowedFileSizeConstraint {
95
	return AllowedFileSizeConstraint{minSize: min, maxSize: max}
96
}
97
98
func (c AllowedFileSizeConstraint) SetUp() error {
99
	return nil
100
}
101
102
func (c AllowedFileSizeConstraint) Name() string {
103
	return "AllowedFileSizeConstraint"
104
}
105
106
func (c AllowedFileSizeConstraint) ValidateFile(file *File, scope validation.Scope) error {
107
	if file == nil {
108
		return nil
109
	}
110
111
	size := len(file.Data)
112
113
	return scope.Validator().ValidateNumber(
114
		size,
115
		it.IsGreaterThanInteger(c.minSize).Message("File size is too small."),
116
		it.IsLessThanInteger(c.maxSize).Message("File size is too large."),
117
	)
118
}
119
120
func ExampleScope_Validator() {
121
	// this constraints will be applied to all files uploaded as avatars
122
	avatarConstraints := []validation.Option{
123
		FileHasAllowedExtension("jpeg", "jpg", "gif"),
124
		FileHasAllowedSize(100, 1000),
125
	}
126
	// this constraints will be applied to all files uploaded as documents
127
	documentConstraints := []validation.Option{
128
		FileHasAllowedExtension("doc", "pdf", "txt"),
129
		FileHasAllowedSize(1000, 100000),
130
	}
131
132
	requests := []FileUploadRequest{
133
		{
134
			Section: "avatars",
135
			File:    &File{Name: "avatar.png", Data: bytes.Repeat([]byte{0}, 99)},
136
		},
137
		{
138
			Section: "documents",
139
			File:    &File{Name: "sheet.xls", Data: bytes.Repeat([]byte{0}, 100001)},
140
		},
141
	}
142
143
	for _, request := range requests {
144
		switch request.Section {
145
		case "avatars":
146
			err := validator.Validate(
147
				// common validation of validatable
148
				validation.Valid(request.File),
149
				// specific validation for file storage section
150
				FileArgument(request.File, avatarConstraints...),
151
			)
152
			fmt.Println(err)
153
		case "documents":
154
			err := validator.Validate(
155
				// common validation of validatable
156
				validation.Valid(request.File),
157
				// specific validation for file storage section
158
				FileArgument(request.File, documentConstraints...),
159
			)
160
			fmt.Println(err)
161
		}
162
	}
163
164
	// Output:
165
	// violation at 'name': Not allowed extension. Must be one of: jpeg, jpg, gif.; violation: File size is too small.
166
	// violation at 'name': Not allowed extension. Must be one of: doc, pdf, txt.; violation: File size is too large.
167
}
168