Passed
Push — main ( 957b58...866640 )
by Igor
02:11 queued 24s
created

it.URLConstraint.WithSchemas   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
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
nop 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.Templates[code.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.Templates[code.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.Templates[code.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.Templates[code.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
	groups                 []string
76
	code                   string
77
	messageTemplate        string
78
	messageParameters      validation.TemplateParameterList
79
}
80
81
// IsURL creates a URLConstraint to validate an URL. By default, constraint checks
82
// only for the http:// and https:// schemas. Use the WithSchemas method to configure
83
// the list of expected schemas. Also, you can use WithRelativeSchema to enable support
84
// of the relative schema (without schema, e.g. "//example.com").
85
func IsURL() URLConstraint {
86 1
	return URLConstraint{
87
		schemas:         []string{"http", "https"},
88
		code:            code.InvalidURL,
89
		messageTemplate: message.Templates[code.InvalidURL],
90
	}
91
}
92
93
// SetUp will return an error if the list of schemas is empty.
94
func (c URLConstraint) SetUp() error {
95 1
	if len(c.schemas) == 0 {
96 1
		return errEmptySchemas
97
	}
98
99 1
	return nil
100
}
101
102
// Name is the constraint name.
103
func (c URLConstraint) Name() string {
104 1
	return "URLConstraint"
105
}
106
107
// WithRelativeSchema enables support of relative URL schema, which means that URL value
108
// may be treated as relative (without schema, e.g. "//example.com").
109
func (c URLConstraint) WithRelativeSchema() URLConstraint {
110 1
	c.supportsRelativeSchema = true
111 1
	return c
112
}
113
114
// WithSchemas is used to set up a list of accepted schemas. For example, if you also consider the ftp:// type URLs
115
// to be valid, redefine the schemas list, listing http, https, and also ftp.
116
// If the list is empty, then an error will be returned by the SetUp method.
117
func (c URLConstraint) WithSchemas(schemas ...string) URLConstraint {
118 1
	c.schemas = schemas
119 1
	return c
120
}
121
122
// Code overrides default code for produced violation.
123
func (c URLConstraint) Code(code string) URLConstraint {
124 1
	c.code = code
125 1
	return c
126
}
127
128
// Message sets the violation message template. You can set custom template parameters
129
// for injecting its values into the final message. Also, you can use default parameters:
130
//
131
//	{{ value }} - the current (invalid) value.
132
func (c URLConstraint) Message(template string, parameters ...validation.TemplateParameter) URLConstraint {
133 1
	c.messageTemplate = template
134 1
	c.messageParameters = parameters
135 1
	return c
136
}
137
138
// When enables conditional validation of this constraint. If the expression evaluates to false,
139
// then the constraint will be ignored.
140
func (c URLConstraint) When(condition bool) URLConstraint {
141 1
	c.isIgnored = !condition
142 1
	return c
143
}
144
145
// WhenGroups enables conditional validation of the constraint by using the validation groups.
146
func (c URLConstraint) WhenGroups(groups ...string) URLConstraint {
147 1
	c.groups = groups
148 1
	return c
149
}
150
151
func (c URLConstraint) ValidateString(value *string, scope validation.Scope) error {
152 1
	if c.isIgnored || scope.IsIgnored(c.groups...) || value == nil || *value == "" {
153 1
		return nil
154
	}
155
156 1
	schemas := c.schemas
157 1
	if c.supportsRelativeSchema {
158 1
		schemas = append(schemas, "")
159
	}
160 1
	if is.URL(*value, schemas...) {
161 1
		return nil
162
	}
163
164 1
	return scope.BuildViolation(c.code, c.messageTemplate).
165
		SetParameters(
166
			c.messageParameters.Prepend(
167
				validation.TemplateParameter{Key: "{{ value }}", Value: *value},
168
			)...,
169
		).
170
		CreateViolation()
171
}
172
173
// IPConstraint is used to validate IP address. You can check for different versions
174
// and restrict some ranges by additional options.
175
type IPConstraint struct {
176
	isIgnored    bool
177
	validate     func(value string, restrictions ...validate.IPRestriction) error
178
	restrictions []validate.IPRestriction
179
180
	groups []string
181
182
	invalidCode    string
183
	prohibitedCode string
184
185
	invalidMessageTemplate      string
186
	invalidMessageParameters    validation.TemplateParameterList
187
	prohibitedMessageTemplate   string
188
	prohibitedMessageParameters validation.TemplateParameterList
189
}
190
191
// IsIP creates an IPConstraint to validate an IP address (IPv4 or IPv6).
192
func IsIP() IPConstraint {
193 1
	return newIPConstraint(validate.IP)
194
}
195
196
// IsIPv4 creates an IPConstraint to validate an IPv4 address.
197
func IsIPv4() IPConstraint {
198 1
	return newIPConstraint(validate.IPv4)
199
}
200
201
// IsIPv6 creates an IPConstraint to validate an IPv4 address.
202
func IsIPv6() IPConstraint {
203 1
	return newIPConstraint(validate.IPv6)
204
}
205
206
func newIPConstraint(validate func(value string, restrictions ...validate.IPRestriction) error) IPConstraint {
207 1
	return IPConstraint{
208
		validate:                  validate,
209
		invalidCode:               code.InvalidIP,
210
		prohibitedCode:            code.ProhibitedIP,
211
		invalidMessageTemplate:    message.Templates[code.InvalidIP],
212
		prohibitedMessageTemplate: message.Templates[code.ProhibitedIP],
213
	}
214
}
215
216
// SetUp always returns no error.
217
func (c IPConstraint) SetUp() error {
218 1
	return nil
219
}
220
221
// Name is the constraint name.
222
func (c IPConstraint) Name() string {
223
	return "IPConstraint"
224
}
225
226
// DenyPrivateIP denies using of private IPs according to RFC 1918 (IPv4 addresses)
227
// and RFC 4193 (IPv6 addresses).
228
func (c IPConstraint) DenyPrivateIP() IPConstraint {
229 1
	c.restrictions = append(c.restrictions, validate.DenyPrivateIP())
230 1
	return c
231
}
232
233
// DenyIP can be used to deny custom range of IP addresses.
234
func (c IPConstraint) DenyIP(restrict func(ip net.IP) bool) IPConstraint {
235 1
	c.restrictions = append(c.restrictions, restrict)
236 1
	return c
237
}
238
239
// InvalidCode overrides default code for violation produced on invalid IP case.
240
func (c IPConstraint) InvalidCode(code string) IPConstraint {
241 1
	c.invalidCode = code
242 1
	return c
243
}
244
245
// ProhibitedCode overrides default code for violation produced on prohibited IP case.
246
func (c IPConstraint) ProhibitedCode(code string) IPConstraint {
247 1
	c.prohibitedCode = code
248 1
	return c
249
}
250
251
// InvalidMessage sets the violation message template for invalid IP case.
252
// You can set custom template parameters for injecting its values into the final message.
253
// Also, you can use default parameters:
254
//
255
//	{{ value }} - the current (invalid) value.
256
func (c IPConstraint) InvalidMessage(template string, parameters ...validation.TemplateParameter) IPConstraint {
257 1
	c.invalidMessageTemplate = template
258 1
	c.invalidMessageParameters = parameters
259 1
	return c
260
}
261
262
// ProhibitedMessage sets the violation message template for prohibited IP case.
263
// You can set custom template parameters for injecting its values into the final message.
264
// Also, you can use default parameters:
265
//
266
//	{{ value }} - the current (invalid) value.
267
func (c IPConstraint) ProhibitedMessage(template string, parameters ...validation.TemplateParameter) IPConstraint {
268 1
	c.prohibitedMessageTemplate = template
269 1
	c.prohibitedMessageParameters = parameters
270 1
	return c
271
}
272
273
// When enables conditional validation of this constraint. If the expression evaluates to false,
274
// then the constraint will be ignored.
275
func (c IPConstraint) When(condition bool) IPConstraint {
276 1
	c.isIgnored = !condition
277 1
	return c
278
}
279
280
// WhenGroups enables conditional validation of the constraint by using the validation groups.
281
func (c IPConstraint) WhenGroups(groups ...string) IPConstraint {
282 1
	c.groups = groups
283 1
	return c
284
}
285
286
func (c IPConstraint) ValidateString(value *string, scope validation.Scope) error {
287 1
	if c.isIgnored || scope.IsIgnored(c.groups...) || value == nil || *value == "" {
288 1
		return nil
289
	}
290
291 1
	return c.validateIP(*value, scope)
292
}
293
294
func (c IPConstraint) validateIP(value string, scope validation.Scope) error {
295 1
	err := c.validate(value, c.restrictions...)
296 1
	if err == nil {
297 1
		return nil
298
	}
299
300 1
	var builder *validation.ViolationBuilder
301 1
	var parameters validation.TemplateParameterList
302
303 1
	if errors.Is(err, validate.ErrProhibited) {
304 1
		builder = scope.BuildViolation(c.prohibitedCode, c.prohibitedMessageTemplate)
305 1
		parameters = c.prohibitedMessageParameters
306
	} else {
307 1
		builder = scope.BuildViolation(c.invalidCode, c.invalidMessageTemplate)
308 1
		parameters = c.invalidMessageParameters
309
	}
310
311 1
	return builder.
312
		SetParameters(
313
			parameters.Prepend(
314
				validation.TemplateParameter{Key: "{{ value }}", Value: value},
315
			)...,
316
		).
317
		CreateViolation()
318
}
319