Passed
Pull Request — main (#50)
by Igor
02:06
created

it.URLConstraint.ValidateString   B

Complexity

Conditions 6

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6

Importance

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