Test Failed
Push — main ( 7dfe2c...03ceef )
by Igor
01:04 queued 12s
created

it.URLConstraint.WithRelativeSchema   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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