Passed
Push — main ( 0f6dda...8fe6b9 )
by Igor
02:04 queued 11s
created

it.URLConstraint.When   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
package it
2
3
import (
4
	"errors"
5
	"net"
6
7
	"github.com/muonsoft/validation"
8
	"github.com/muonsoft/validation/code"
9
	"github.com/muonsoft/validation/is"
10
	"github.com/muonsoft/validation/message"
11
	"github.com/muonsoft/validation/validate"
12
)
13
14
// IsEmail is used for simplified validation of an email address. It allows all values
15
// with an "@" symbol in, and a "." in the second host part of the email address.
16
func IsEmail() validation.CustomStringConstraint {
17 1
	return validation.NewCustomStringConstraint(
18
		is.Email,
19
		"EmailConstraint",
20
		code.InvalidEmail,
21
		message.InvalidEmail,
22
	)
23
}
24
25
// IsHTML5Email is used for validation of an email address based on pattern for HTML5
26
// (see https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address).
27
func IsHTML5Email() validation.CustomStringConstraint {
28 1
	return validation.NewCustomStringConstraint(
29
		is.HTML5Email,
30
		"HTML5EmailConstraint",
31
		code.InvalidEmail,
32
		message.InvalidEmail,
33
	)
34
}
35
36
// IsHostname validates that a value is a valid hostname. It checks that:
37
//	• each label within a valid hostname may be no more than 63 octets long;
38
//	• the total length of the hostname must not exceed 255 characters;
39
//	• hostname is fully qualified and include its top-level domain name
40
//	  (for instance, example.com is valid but example is not);
41
//	• checks for reserved top-level domains according to RFC 2606
42
//	  (hostnames containing them are not considered valid:
43
//	  .example, .invalid, .localhost, and .test).
44
//
45
// If you do not want to check for top-level domains use IsLooseHostname version of constraint.
46
func IsHostname() validation.CustomStringConstraint {
47 1
	return validation.NewCustomStringConstraint(
48
		is.StrictHostname,
49
		"HostnameConstraint",
50
		code.InvalidHostname,
51
		message.InvalidHostname,
52
	)
53
}
54
55
// IsLooseHostname validates that a value is a valid hostname. It checks that:
56
//	• each label within a valid hostname may be no more than 63 octets long;
57
//	• the total length of the hostname must not exceed 255 characters.
58
func IsLooseHostname() validation.CustomStringConstraint {
59 1
	return validation.NewCustomStringConstraint(
60
		is.Hostname,
61
		"LooseHostnameConstraint",
62
		code.InvalidHostname,
63
		message.InvalidHostname,
64
	)
65
}
66
67
// URLConstraint is used to validate URL string. This constraint doesn’t check that the host of the
68
// given URL really exists, because the information of the DNS records is not reliable.
69
//
70
// This constraint doesn't check the length of the URL. Use LengthConstraint to check the length of the given value.
71
type URLConstraint struct {
72
	isIgnored              bool
73
	supportsRelativeSchema bool
74
	schemas                []string
75
	messageTemplate        string
76
	messageParameters      validation.TemplateParameterList
77
}
78
79
// IsURL creates a URLConstraint to validate an URL. By default, constraint checks
80
// only for the http:// and https:// schemas. Use the WithSchemas method to configure
81
// the list of expected schemas. Also, you can use WithRelativeSchema to enable support
82
// of the relative schema (without schema, e.g. "//example.com").
83
func IsURL() URLConstraint {
84 1
	return URLConstraint{
85
		schemas:         []string{"http", "https"},
86
		messageTemplate: message.InvalidURL,
87
	}
88
}
89
90
// SetUp will return an error if the list of schemas is empty.
91
func (c URLConstraint) SetUp() error {
92 1
	if len(c.schemas) == 0 {
93 1
		return errEmptySchemas
94
	}
95
96 1
	return nil
97
}
98
99
// Name is the constraint name.
100
func (c URLConstraint) Name() string {
101 1
	return "URLConstraint"
102
}
103
104
// WithRelativeSchema enables support of relative URL schema, which means that URL value
105
// may be treated as relative (without schema, e.g. "//example.com").
106
func (c URLConstraint) WithRelativeSchema() URLConstraint {
107 1
	c.supportsRelativeSchema = true
108 1
	return c
109
}
110
111
// WithSchemas is used to set up a list of accepted schemas. For example, if you also consider the ftp:// type URLs
112
// to be valid, redefine the schemas list, listing http, https, and also ftp.
113
// If the list is empty, then an error will be returned by the SetUp method.
114
func (c URLConstraint) WithSchemas(schemas ...string) URLConstraint {
115 1
	c.schemas = schemas
116 1
	return c
117
}
118
119
// Message sets the violation message template. You can set custom template parameters
120
// for injecting its values into the final message. Also, you can use default parameters:
121
//
122
//	{{ value }} - the current (invalid) value.
123
func (c URLConstraint) Message(template string, parameters ...validation.TemplateParameter) URLConstraint {
124 1
	c.messageTemplate = template
125 1
	c.messageParameters = parameters
126 1
	return c
127
}
128
129
// When enables conditional validation of this constraint. If the expression evaluates to false,
130
// then the constraint will be ignored.
131
func (c URLConstraint) When(condition bool) URLConstraint {
132 1
	c.isIgnored = !condition
133 1
	return c
134
}
135
136
func (c URLConstraint) ValidateString(value *string, scope validation.Scope) error {
137 1
	if c.isIgnored || value == nil || *value == "" {
138 1
		return nil
139
	}
140
141 1
	schemas := c.schemas
142 1
	if c.supportsRelativeSchema {
143 1
		schemas = append(schemas, "")
144
	}
145 1
	if is.URL(*value, schemas...) {
146 1
		return nil
147
	}
148
149 1
	return scope.BuildViolation(code.InvalidURL, c.messageTemplate).
150
		SetParameters(
151
			c.messageParameters.Prepend(
152
				validation.TemplateParameter{Key: "{{ value }}", Value: *value},
153
			)...,
154
		).
155
		AddParameter("{{ value }}", *value).
156
		CreateViolation()
157
}
158
159
// IPConstraint is used to validate IP address. You can check for different versions
160
// and restrict some ranges by additional options.
161
type IPConstraint struct {
162
	isIgnored    bool
163
	validate     func(value string, restrictions ...validate.IPRestriction) error
164
	restrictions []validate.IPRestriction
165
166
	invalidMessageTemplate      string
167
	invalidMessageParameters    validation.TemplateParameterList
168
	prohibitedMessageTemplate   string
169
	prohibitedMessageParameters validation.TemplateParameterList
170
}
171
172
// IsIP creates an IPConstraint to validate an IP address (IPv4 or IPv6).
173
func IsIP() IPConstraint {
174 1
	return newIPConstraint(validate.IP)
175
}
176
177
// IsIPv4 creates an IPConstraint to validate an IPv4 address.
178
func IsIPv4() IPConstraint {
179 1
	return newIPConstraint(validate.IPv4)
180
}
181
182
// IsIPv6 creates an IPConstraint to validate an IPv4 address.
183
func IsIPv6() IPConstraint {
184 1
	return newIPConstraint(validate.IPv6)
185
}
186
187
func newIPConstraint(validate func(value string, restrictions ...validate.IPRestriction) error) IPConstraint {
188 1
	return IPConstraint{
189
		validate:                  validate,
190
		invalidMessageTemplate:    message.InvalidIP,
191
		prohibitedMessageTemplate: message.ProhibitedIP,
192
	}
193
}
194
195
// SetUp always returns no error.
196
func (c IPConstraint) SetUp() error {
197 1
	return nil
198
}
199
200
// Name is the constraint name.
201
func (c IPConstraint) Name() string {
202
	return "IPConstraint"
203
}
204
205
// DenyPrivateIP denies using of private IPs according to RFC 1918 (IPv4 addresses)
206
// and RFC 4193 (IPv6 addresses).
207
func (c IPConstraint) DenyPrivateIP() IPConstraint {
208 1
	c.restrictions = append(c.restrictions, validate.DenyPrivateIP())
209 1
	return c
210
}
211
212
// DenyIP can be used to deny custom range of IP addresses.
213
func (c IPConstraint) DenyIP(restrict func(ip net.IP) bool) IPConstraint {
214 1
	c.restrictions = append(c.restrictions, restrict)
215 1
	return c
216
}
217
218
// InvalidMessage sets the violation message template for invalid IP case.
219
// You can set custom template parameters for injecting its values into the final message.
220
// Also, you can use default parameters:
221
//
222
//	{{ value }} - the current (invalid) value.
223
func (c IPConstraint) InvalidMessage(template string, parameters ...validation.TemplateParameter) IPConstraint {
224 1
	c.invalidMessageTemplate = template
225 1
	c.invalidMessageParameters = parameters
226 1
	return c
227
}
228
229
// ProhibitedMessage sets the violation message template for prohibited IP case.
230
// You can set custom template parameters for injecting its values into the final message.
231
// Also, you can use default parameters:
232
//
233
//	{{ value }} - the current (invalid) value.
234
func (c IPConstraint) ProhibitedMessage(template string, parameters ...validation.TemplateParameter) IPConstraint {
235 1
	c.prohibitedMessageTemplate = template
236 1
	c.prohibitedMessageParameters = parameters
237 1
	return c
238
}
239
240
// When enables conditional validation of this constraint. If the expression evaluates to false,
241
// then the constraint will be ignored.
242
func (c IPConstraint) When(condition bool) IPConstraint {
243 1
	c.isIgnored = !condition
244 1
	return c
245
}
246
247
func (c IPConstraint) ValidateString(value *string, scope validation.Scope) error {
248 1
	if c.isIgnored || value == nil || *value == "" {
249 1
		return nil
250
	}
251
252 1
	return c.validateIP(*value, scope)
253
}
254
255
func (c IPConstraint) validateIP(value string, scope validation.Scope) error {
256 1
	err := c.validate(value, c.restrictions...)
257 1
	if err == nil {
258 1
		return nil
259
	}
260
261 1
	var builder *validation.ViolationBuilder
262 1
	var parameters validation.TemplateParameterList
263
264 1
	if errors.Is(err, validate.ErrProhibited) {
265 1
		builder = scope.BuildViolation(code.ProhibitedIP, c.prohibitedMessageTemplate)
266 1
		parameters = c.prohibitedMessageParameters
267
	} else {
268 1
		builder = scope.BuildViolation(code.InvalidIP, c.invalidMessageTemplate)
269 1
		parameters = c.invalidMessageParameters
270
	}
271
272 1
	return builder.
273
		SetParameters(
274
			parameters.Prepend(
275
				validation.TemplateParameter{Key: "{{ value }}", Value: value},
276
			)...,
277
		).
278
		CreateViolation()
279
}
280