Passed
Push — main ( 0f61b2...8dabea )
by Rushan
02:09 queued 11s
created

it.IPConstraint.ValidateString   A

Complexity

Conditions 4

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

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