line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package TUWF::Validate; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
146052
|
use strict; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
98
|
|
4
|
3
|
|
|
3
|
|
24
|
use warnings; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
90
|
|
5
|
3
|
|
|
3
|
|
25
|
use Carp 'croak'; |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
155
|
|
6
|
3
|
|
|
3
|
|
18
|
use Exporter 'import'; |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
119
|
|
7
|
3
|
|
|
3
|
|
22
|
use Scalar::Util 'blessed'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
10933
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our @EXPORT_OK = qw/compile validate/; |
10
|
|
|
|
|
|
|
our $VERSION = '1.5'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# Unavailable as custom validation names |
14
|
|
|
|
|
|
|
my %builtin = map +($_,1), qw/ |
15
|
|
|
|
|
|
|
type |
16
|
|
|
|
|
|
|
required default |
17
|
|
|
|
|
|
|
onerror |
18
|
|
|
|
|
|
|
rmwhitespace |
19
|
|
|
|
|
|
|
values scalar sort unique |
20
|
|
|
|
|
|
|
keys unknown |
21
|
|
|
|
|
|
|
func |
22
|
|
|
|
|
|
|
/; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub _length { |
26
|
99
|
|
|
99
|
|
173
|
my($exp, $min, $max) = @_; |
27
|
|
|
|
|
|
|
+{ _analyze_minlength => $min, _analyze_maxlength => $max, func => sub { |
28
|
94
|
100
|
|
94
|
|
247
|
my $got = ref $_[0] eq 'HASH' ? keys %{$_[0]} : ref $_[0] eq 'ARRAY' ? @{$_[0]} : length $_[0]; |
|
6
|
100
|
|
|
|
18
|
|
|
4
|
|
|
|
|
9
|
|
29
|
94
|
100
|
100
|
|
|
481
|
(!defined $min || $got >= $min) && (!defined $max || $got <= $max) ? 1 : { expected => $exp, got => $got }; |
30
|
|
|
|
|
|
|
}} |
31
|
99
|
|
|
|
|
657
|
} |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# Basically the same as ( regex => $arg ), but hides the regex error |
34
|
|
|
|
|
|
|
sub _reg { |
35
|
27
|
|
|
27
|
|
41
|
my $reg = $_[0]; |
36
|
27
|
100
|
|
128
|
|
177
|
( type => 'scalar', _analyze_regex => "$reg", func => sub { $_[0] =~ $reg ? 1 : { got => $_[0] } } ); |
|
128
|
|
|
|
|
1008
|
|
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
our $re_num = qr/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/; |
41
|
|
|
|
|
|
|
my $re_int = qr/^-?(?:0|[1-9]\d*)$/; |
42
|
|
|
|
|
|
|
our $re_uint = qr/^(?:0|[1-9]\d*)$/; |
43
|
|
|
|
|
|
|
my $re_fqdn = qr/(?:[a-zA-Z0-9][\w-]*\.)+[a-zA-Z][a-zA-Z0-9-]{1,25}\.?/; |
44
|
|
|
|
|
|
|
my $re_ip4_digit = qr/(?:0|[1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/; |
45
|
|
|
|
|
|
|
my $re_ip4 = qr/($re_ip4_digit\.){3}$re_ip4_digit/; |
46
|
|
|
|
|
|
|
# This monstrosity is based on http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses |
47
|
|
|
|
|
|
|
# Doesn't allow IPv4-mapped-IPv6 addresses or other fancy stuff. |
48
|
|
|
|
|
|
|
my $re_ip6 = qr/(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)/; |
49
|
|
|
|
|
|
|
my $re_ip = qr/(?:$re_ip4|$re_ip6)/; |
50
|
|
|
|
|
|
|
my $re_domain = qr/(?:$re_fqdn|$re_ip4|\[$re_ip6\])/; |
51
|
|
|
|
|
|
|
# Also used by the TUWF::Misc::kv_validate() |
52
|
|
|
|
|
|
|
our $re_email = qr/^[-\+\.#\$=\w]+\@$re_fqdn$/; |
53
|
|
|
|
|
|
|
our $re_weburl = qr/^https?:\/\/$re_domain(?::[1-9][0-9]{0,5})?(?:\/[^\s<>"]*)$/; |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
our %default_validations = ( |
57
|
|
|
|
|
|
|
regex => sub { |
58
|
|
|
|
|
|
|
my $reg = shift; |
59
|
|
|
|
|
|
|
# Error objects should be plain data structures so that they can easily |
60
|
|
|
|
|
|
|
# be converted to JSON for debugging. We have to stringify $reg in the |
61
|
|
|
|
|
|
|
# error object to ensure that. |
62
|
|
|
|
|
|
|
+{ type => 'scalar', _analyze_regex => "$reg", func => sub { $_[0] =~ $reg ? 1 : { regex => "$reg", got => $_[0] } } } |
63
|
|
|
|
|
|
|
}, |
64
|
|
|
|
|
|
|
enum => sub { |
65
|
|
|
|
|
|
|
my @l = ref $_[0] eq 'HASH' ? sort keys %{$_[0]} : ref $_[0] eq 'ARRAY' ? @{$_[0]} : ($_[0]); |
66
|
|
|
|
|
|
|
my %opts = map +($_,1), @l; |
67
|
|
|
|
|
|
|
+{ type => 'scalar', func => sub { $opts{ (my $v = $_[0]) } ? 1 : { expected => \@l, got => $_[0] } } } |
68
|
|
|
|
|
|
|
}, |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
minlength => sub { _length $_[0], $_[0] }, |
71
|
|
|
|
|
|
|
maxlength => sub { _length $_[0], undef, $_[0] }, |
72
|
|
|
|
|
|
|
length => sub { _length($_[0], ref $_[0] eq 'ARRAY' ? @{$_[0]} : ($_[0], $_[0])) }, |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
anybool => { type => 'any', required => 0, default => 0, func => sub { $_[0] = $_[0] ? 1 : 0; 1 } }, |
75
|
|
|
|
|
|
|
undefbool => { type => 'any', required => 0, default => undef, func => sub { $_[0] = $_[0] ? 1 : 0; 1 } }, |
76
|
|
|
|
|
|
|
jsonbool => { type => 'any', func => sub { |
77
|
|
|
|
|
|
|
my $r = $_[0]; |
78
|
|
|
|
|
|
|
blessed $r && ( |
79
|
|
|
|
|
|
|
$r->isa('JSON::PP::Boolean') |
80
|
|
|
|
|
|
|
|| $r->isa('JSON::XS::Boolean') |
81
|
|
|
|
|
|
|
|| $r->isa('Types::Serialiser::Boolean') |
82
|
|
|
|
|
|
|
|| $r->isa('Cpanel::JSON::XS::Boolean') |
83
|
|
|
|
|
|
|
|| $r->isa('boolean') |
84
|
|
|
|
|
|
|
) ? 1 : {}; |
85
|
|
|
|
|
|
|
} }, |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# JSON number format, regex from http://stackoverflow.com/questions/13340717/json-numbers-regular-expression |
88
|
|
|
|
|
|
|
num => { _reg $re_num }, |
89
|
|
|
|
|
|
|
int => { _reg $re_int }, # implies num |
90
|
|
|
|
|
|
|
uint => { _reg $re_uint }, # implies num |
91
|
|
|
|
|
|
|
min => sub { |
92
|
|
|
|
|
|
|
my $min = shift; |
93
|
|
|
|
|
|
|
+{ num => 1, _analyze_min => $min, func => sub { $_[0] >= $min ? 1 : { expected => $min, got => $_[0] } } } |
94
|
|
|
|
|
|
|
}, |
95
|
|
|
|
|
|
|
max => sub { |
96
|
|
|
|
|
|
|
my $max = shift; |
97
|
|
|
|
|
|
|
+{ num => 1, _analyze_max => $max, func => sub { $_[0] <= $max ? 1 : { expected => $max, got => $_[0] } } } |
98
|
|
|
|
|
|
|
}, |
99
|
|
|
|
|
|
|
range => sub { +{ min => $_[0][0], max => $_[0][1] } }, |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
ascii => { _reg qr/^[\x20-\x7E]*$/ }, |
102
|
|
|
|
|
|
|
ipv4 => { _reg $re_ip4 }, |
103
|
|
|
|
|
|
|
ipv6 => { _reg $re_ip6 }, |
104
|
|
|
|
|
|
|
ip => { _reg $re_ip }, |
105
|
|
|
|
|
|
|
email => { _reg($re_email), maxlength => 254 }, |
106
|
|
|
|
|
|
|
weburl => { _reg($re_weburl), maxlength => 65536 }, # the maxlength is a bit arbitrary, but better than unlimited |
107
|
|
|
|
|
|
|
); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Loads a hashref of validations and a schema definition, and converts it into |
111
|
|
|
|
|
|
|
# an object with: |
112
|
|
|
|
|
|
|
# { |
113
|
|
|
|
|
|
|
# name => $name_or_undef, |
114
|
|
|
|
|
|
|
# validations => [ $recursive_compiled_object, .. ], |
115
|
|
|
|
|
|
|
# schema => $modified_schema_without_validations, |
116
|
|
|
|
|
|
|
# known_keys => { $key => 1, .. } # Extracted from 'keys', Used for the 'unknown' validation |
117
|
|
|
|
|
|
|
# } |
118
|
|
|
|
|
|
|
sub _compile { |
119
|
1017
|
|
|
1017
|
|
1930
|
my($validations, $schema, $rec) = @_; |
120
|
|
|
|
|
|
|
|
121
|
1017
|
|
|
|
|
1319
|
my(%top, @val); |
122
|
1017
|
100
|
|
|
|
2088
|
my @keys = keys %{$schema->{keys}} if $schema->{keys}; |
|
47
|
|
|
|
|
161
|
|
123
|
|
|
|
|
|
|
|
124
|
1017
|
|
|
|
|
3048
|
for(sort keys %$schema) { |
125
|
1944
|
100
|
100
|
|
|
6143
|
if($builtin{$_} || /^_analyze_/) { |
126
|
1372
|
|
|
|
|
2378
|
$top{$_} = $schema->{$_}; |
127
|
1372
|
|
|
|
|
2113
|
next; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
572
|
|
100
|
|
|
1562
|
my $t = $validations->{$_} || $default_validations{$_}; |
131
|
572
|
100
|
|
|
|
1245
|
croak "Unknown validation: $_" if !$t; |
132
|
570
|
100
|
|
|
|
1725
|
croak "Recursion limit exceeded while resolving validation '$_'" if $rec < 1; |
133
|
568
|
100
|
|
|
|
1308
|
$t = ref $t eq 'HASH' ? $t : $t->($schema->{$_}); |
134
|
|
|
|
|
|
|
|
135
|
568
|
|
|
|
|
1480
|
my $v = _compile($validations, $t, $rec-1); |
136
|
440
|
|
|
|
|
806
|
$v->{name} = $_; |
137
|
440
|
|
|
|
|
1044
|
push @val, $v; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# Inherit some builtin options from validations |
141
|
885
|
|
|
|
|
1621
|
for my $t (@val) { |
142
|
440
|
100
|
100
|
|
|
1100
|
if($top{type} && $t->{schema}{type} && $top{type} ne $t->{schema}{type}) { |
|
|
|
100
|
|
|
|
|
143
|
2
|
50
|
|
|
|
169
|
croak "Incompatible types, the schema specifies '$top{type}' but validation '$t->{name}' requires '$t->{schema}{type}'" if $schema->{type}; |
144
|
0
|
|
|
|
|
0
|
croak "Incompatible types, '$t->[0]' requires '$t->{schema}{type}', but another validation requires '$top{type}'"; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
exists $t->{schema}{$_} and !exists $top{$_} and $top{$_} = delete $t->{schema}{$_} |
147
|
438
|
|
100
|
|
|
4793
|
for qw/required default onerror rmwhitespace type scalar unknown sort unique/; |
|
|
|
66
|
|
|
|
|
148
|
|
|
|
|
|
|
|
149
|
438
|
|
|
|
|
650
|
push @keys, keys %{ delete $t->{known_keys} }; |
|
438
|
|
|
|
|
889
|
|
150
|
438
|
100
|
|
|
|
1162
|
push @keys, keys %{ $t->{schema}{keys} } if $t->{schema}{keys}; |
|
16
|
|
|
|
|
50
|
|
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Compile sub-schemas |
154
|
883
|
100
|
|
|
|
1566
|
$top{keys} = { map +($_, compile($validations, $top{keys}{$_})), keys %{$top{keys}} } if $top{keys}; |
|
47
|
|
|
|
|
169
|
|
155
|
882
|
100
|
|
|
|
1557
|
$top{values} = compile($validations, $top{values}) if $top{values}; |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# XXX: Flattening recursive validations would be faster and may simplify |
158
|
|
|
|
|
|
|
# the code a bit, but makes error objects harder to interpret. |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# XXX: As an optimization, it's possible to remove double validations (e.g. |
161
|
|
|
|
|
|
|
# multiple invocations of the same validation with the same options due to |
162
|
|
|
|
|
|
|
# validations calling each other). Care must be taken that this won't |
163
|
|
|
|
|
|
|
# affect error objects (i.e. only subsequent invocations should be |
164
|
|
|
|
|
|
|
# removed). |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
return { |
167
|
882
|
|
|
|
|
3977
|
validations => \@val, |
168
|
|
|
|
|
|
|
schema => \%top, |
169
|
|
|
|
|
|
|
known_keys => { map +($_,1), @keys }, |
170
|
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub compile { |
175
|
449
|
|
|
449
|
0
|
25494
|
my($validations, $schema) = @_; |
176
|
|
|
|
|
|
|
|
177
|
449
|
50
|
|
|
|
1230
|
return $schema if ref $schema eq __PACKAGE__; |
178
|
|
|
|
|
|
|
|
179
|
449
|
|
|
|
|
904
|
my $c = _compile $validations, $schema, 64; |
180
|
|
|
|
|
|
|
|
181
|
442
|
|
100
|
|
|
1323
|
$c->{schema}{type} //= 'scalar'; |
182
|
442
|
|
100
|
|
|
1531
|
$c->{schema}{required} //= 1; |
183
|
442
|
|
100
|
|
|
1589
|
$c->{schema}{rmwhitespace} //= 1; |
184
|
442
|
|
100
|
|
|
1419
|
$c->{schema}{unknown} //= 'remove'; |
185
|
|
|
|
|
|
|
|
186
|
442
|
100
|
|
|
|
835
|
if(exists $c->{schema}{sort}) { |
187
|
10
|
|
|
|
|
17
|
my $s = $c->{schema}{sort}; |
188
|
|
|
|
|
|
|
$c->{schema}{sort} = |
189
|
|
|
|
|
|
|
ref $s eq 'CODE' ? $s |
190
|
6
|
|
|
6
|
|
14
|
: $s eq 'str' ? sub { $_[0] cmp $_[1] } |
191
|
22
|
|
|
22
|
|
70
|
: $s eq 'num' ? sub { $_[0] <=> $_[1] } |
192
|
10
|
50
|
|
|
|
58
|
: croak "Unknown value for 'sort': $c->{schema}{sort}"; |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
193
|
|
|
|
|
|
|
} |
194
|
442
|
100
|
100
|
12
|
|
938
|
$c->{schema}{unique} = sub { $_[0] } if $c->{schema}{unique} && !ref $c->{schema}{unique} && !$c->{schema}{sort}; |
|
12
|
|
100
|
|
|
18
|
|
195
|
|
|
|
|
|
|
|
196
|
442
|
|
|
|
|
1280
|
bless $c, __PACKAGE__; |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub _validate_rec { |
201
|
660
|
|
|
660
|
|
1034
|
my($c, $input) = @_; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# hash keys |
204
|
660
|
100
|
|
|
|
1145
|
if($c->{schema}{keys}) { |
205
|
32
|
|
|
|
|
41
|
my @err; |
206
|
32
|
|
|
|
|
41
|
for my $k (keys %{$c->{schema}{keys}}) { |
|
32
|
|
|
|
|
86
|
|
207
|
|
|
|
|
|
|
# We need to overload the '!exists && !required && !default' |
208
|
|
|
|
|
|
|
# scenario a bit, because in that case we should not create the key |
209
|
|
|
|
|
|
|
# in the output. All other cases will be handled just fine by |
210
|
|
|
|
|
|
|
# passing an implicit 'undef'. |
211
|
44
|
|
|
|
|
84
|
my $s = $c->{schema}{keys}{$k}; |
212
|
44
|
100
|
100
|
|
|
162
|
next if !exists $input->{$k} && !$s->{schema}{required} && !exists $s->{schema}{default}; |
|
|
|
100
|
|
|
|
|
213
|
|
|
|
|
|
|
|
214
|
30
|
|
|
|
|
62
|
my $r = _validate($s, $input->{$k}); |
215
|
30
|
|
|
|
|
60
|
$input->{$k} = $r->[0]; |
216
|
30
|
100
|
|
|
|
71
|
if($r->[1]) { |
217
|
8
|
|
|
|
|
15
|
$r->[1]{key} = $k; |
218
|
8
|
|
|
|
|
22
|
push @err, $r->[1]; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
} |
221
|
32
|
100
|
|
|
|
93
|
return [$input, { validation => 'keys', errors => \@err }] if @err; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# array values |
225
|
652
|
100
|
|
|
|
1111
|
if($c->{schema}{values}) { |
226
|
10
|
|
|
|
|
17
|
my @err; |
227
|
10
|
|
|
|
|
26
|
for my $i (0..$#$input) { |
228
|
18
|
|
|
|
|
36
|
my $r = _validate($c->{schema}{values}, $input->[$i]); |
229
|
18
|
|
|
|
|
26
|
$input->[$i] = $r->[0]; |
230
|
18
|
100
|
|
|
|
42
|
if($r->[1]) { |
231
|
2
|
|
|
|
|
4
|
$r->[1]{index} = $i; |
232
|
2
|
|
|
|
|
8
|
push @err, $r->[1]; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
} |
235
|
10
|
100
|
|
|
|
26
|
return [$input, { validation => 'values', errors => \@err }] if @err; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# validations |
239
|
650
|
|
|
|
|
744
|
for (@{$c->{validations}}) { |
|
650
|
|
|
|
|
1200
|
|
240
|
344
|
|
|
|
|
576
|
my $r = _validate_rec($_, $input); |
241
|
344
|
|
|
|
|
618
|
$input = $r->[0]; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
return [$input, { |
244
|
|
|
|
|
|
|
# If the error was a custom 'func' object, then make that the primary cause. |
245
|
|
|
|
|
|
|
# This makes it possible for validations to provide their own error objects. |
246
|
104
|
|
|
|
|
584
|
$r->[1]{validation} eq 'func' && (!exists $r->[1]{result} || keys %{$r->[1]} > 2) ? %{$r->[1]} : (error => $r->[1]), |
247
|
|
|
|
|
|
|
validation => $_->{name}, |
248
|
344
|
100
|
100
|
|
|
1108
|
}] if $r->[1]; |
|
|
100
|
|
|
|
|
|
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# func |
252
|
526
|
100
|
|
|
|
1007
|
if($c->{schema}{func}) { |
253
|
306
|
|
|
|
|
658
|
my $r = $c->{schema}{func}->($input); |
254
|
306
|
100
|
|
|
|
1215
|
return [$input, { %$r, validation => 'func' }] if ref $r eq 'HASH'; |
255
|
202
|
100
|
|
|
|
376
|
return [$input, { validation => 'func', result => $r }] if !$r; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
420
|
|
|
|
|
841
|
return [$input] |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
sub _validate_array { |
263
|
200
|
|
|
200
|
|
320
|
my($c, $input) = @_; |
264
|
|
|
|
|
|
|
|
265
|
200
|
100
|
|
|
|
623
|
return [$input] if $c->{schema}{type} ne 'array'; |
266
|
|
|
|
|
|
|
|
267
|
44
|
100
|
|
|
|
107
|
$input = [sort { $c->{schema}{sort}->($a,$b) } @$input ] if $c->{schema}{sort}; |
|
26
|
|
|
|
|
61
|
|
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Key-based uniqueness |
270
|
44
|
100
|
100
|
|
|
147
|
if($c->{schema}{unique} && ref $c->{schema}{unique} eq 'CODE') { |
|
|
100
|
|
|
|
|
|
271
|
8
|
|
|
|
|
13
|
my %h; |
272
|
8
|
|
|
|
|
19
|
for my $i (0..$#$input) { |
273
|
24
|
|
|
|
|
49
|
my $k = $c->{schema}{unique}->($input->[$i]); |
274
|
24
|
100
|
|
|
|
102
|
return [$input, { validation => 'unique', index_a => $h{$k}, value_a => $input->[$h{$k}], index_b => $i, value_b => $input->[$i], key => $k }] if exists $h{$k}; |
275
|
20
|
|
|
|
|
38
|
$h{$k} = $i; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Comparison-based uniqueness |
279
|
|
|
|
|
|
|
} elsif($c->{schema}{unique}) { |
280
|
4
|
|
|
|
|
25
|
for my $i (0..$#$input-1) { |
281
|
|
|
|
|
|
|
return [$input, { validation => 'unique', index_a => $i, value_a => $input->[$i], index_b => $i+1, value_b => $input->[$i+1] }] |
282
|
8
|
100
|
|
|
|
19
|
if $c->{schema}{sort}->($input->[$i], $input->[$i+1]) == 0 |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
38
|
|
|
|
|
93
|
return [$input] |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub _validate_input { |
291
|
373
|
|
|
373
|
|
560
|
my($c, $input) = @_; |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# rmwhitespace (needs to be done before the 'required' test) |
294
|
373
|
100
|
100
|
|
|
1773
|
if(defined $input && !ref $input && $c->{schema}{type} eq 'scalar' && $c->{schema}{rmwhitespace}) { |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
295
|
212
|
|
|
|
|
513
|
$input =~ s/\r//g; |
296
|
212
|
|
|
|
|
913
|
$input =~ s/^\s*//; |
297
|
212
|
|
|
|
|
903
|
$input =~ s/\s*$//; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# required & default |
301
|
373
|
100
|
100
|
|
|
1504
|
if(!defined $input || (!ref $input && $input eq '')) { |
|
|
|
100
|
|
|
|
|
302
|
|
|
|
|
|
|
# XXX: This will return undef if !required and no default is set, even for hash and array types. Should those get an empty hash or array? |
303
|
44
|
100
|
|
|
|
172
|
return [ref $c->{schema}{default} eq 'CODE' ? $c->{schema}{default}->($input) : exists $c->{schema}{default} ? $c->{schema}{default} : $input] if !$c->{schema}{required}; |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
304
|
22
|
|
|
|
|
106
|
return [$input, { validation => 'required' }]; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
329
|
100
|
|
|
|
824
|
if($c->{schema}{type} eq 'scalar') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
308
|
210
|
100
|
|
|
|
373
|
return [$input, { validation => 'type', expected => 'scalar', got => lc ref $input }] if ref $input; |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
} elsif($c->{schema}{type} eq 'hash') { |
311
|
46
|
100
|
100
|
|
|
132
|
return [$input, { validation => 'type', expected => 'hash', got => lc ref $input || 'scalar' }] if ref $input ne 'HASH'; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# unknown |
314
|
|
|
|
|
|
|
# Each branch below makes a shallow copy of the hash, so that further |
315
|
|
|
|
|
|
|
# validations can perform in-place modifications without affecting the |
316
|
|
|
|
|
|
|
# input. |
317
|
40
|
100
|
|
|
|
106
|
if($c->{schema}{unknown} eq 'remove') { |
|
|
100
|
|
|
|
|
|
318
|
20
|
|
|
|
|
72
|
$input = { map +($_, $input->{$_}), grep $c->{known_keys}{$_}, keys %$input }; |
319
|
|
|
|
|
|
|
} elsif($c->{schema}{unknown} eq 'reject') { |
320
|
4
|
|
|
|
|
17
|
my @err = grep !$c->{known_keys}{$_}, keys %$input; |
321
|
4
|
100
|
|
|
|
14
|
return [$input, { validation => 'unknown', keys => \@err, expected => [ sort keys %{$c->{known_keys}} ] }] if @err; |
|
2
|
|
|
|
|
13
|
|
322
|
2
|
|
|
|
|
8
|
$input = { %$input }; |
323
|
|
|
|
|
|
|
} else { |
324
|
16
|
|
|
|
|
51
|
$input = { %$input }; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
} elsif($c->{schema}{type} eq 'array') { |
328
|
50
|
100
|
66
|
|
|
117
|
$input = [$input] if $c->{schema}{scalar} && !ref $input; |
329
|
50
|
50
|
50
|
|
|
115
|
return [$input, { validation => 'type', expected => $c->{schema}{scalar} ? 'array or scalar' : 'array', got => lc ref $input || 'scalar' }] if ref $input ne 'ARRAY'; |
|
|
100
|
|
|
|
|
|
330
|
48
|
|
|
|
|
94
|
$input = [@$input]; # Create a shallow copy to prevent in-place modification. |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
} elsif($c->{schema}{type} eq 'any') { |
333
|
|
|
|
|
|
|
# No need to do anything here. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
} else { |
336
|
1
|
|
|
|
|
119
|
croak "Unknown type '$c->{schema}{type}'"; # Should be checked in _compile(), preferably. |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
316
|
|
|
|
|
594
|
my $r = _validate_rec($c, $input); |
340
|
316
|
100
|
|
|
|
718
|
return $r if $r->[1]; |
341
|
200
|
|
|
|
|
289
|
$input = $r->[0]; |
342
|
|
|
|
|
|
|
|
343
|
200
|
|
|
|
|
357
|
_validate_array($c, $input); |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub _validate { |
348
|
373
|
|
|
373
|
|
587
|
my($c, $input) = @_; |
349
|
373
|
|
|
|
|
636
|
my $r = _validate_input($c, $input); |
350
|
372
|
100
|
100
|
|
|
2261
|
$r->[1] && exists $c->{schema}{onerror} ? [ref $c->{schema}{onerror} eq 'CODE' ? $c->{schema}{onerror}->(bless $r, 'TUWF::Validate::Result') : $c->{schema}{onerror}] : $r |
|
|
100
|
|
|
|
|
|
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
sub validate { |
355
|
325
|
100
|
|
325
|
0
|
85669
|
my($c, $input) = ref $_[0] eq __PACKAGE__ ? @_ : (compile($_[0], $_[1]), $_[2]); |
356
|
325
|
|
|
|
|
633
|
bless _validate($c, $input), 'TUWF::Validate::Result'; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
sub analyze { |
361
|
41
|
|
|
41
|
0
|
236
|
require TUWF::Validate::Interop; |
362
|
41
|
|
|
|
|
122
|
TUWF::Validate::Interop::analyze($_[0]); |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
package TUWF::Validate::Result; |
368
|
|
|
|
|
|
|
|
369
|
3
|
|
|
3
|
|
35
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
102
|
|
370
|
3
|
|
|
3
|
|
18
|
use warnings; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
118
|
|
371
|
3
|
|
|
3
|
|
20
|
use Carp 'croak'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
262
|
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
# A result object contains: [$data, $error] |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# In boolean context, returns whether the validation succeeded. |
376
|
3
|
|
|
3
|
|
3840
|
use overload bool => sub { !$_[0][1] }; |
|
3
|
|
|
162
|
|
3142
|
|
|
3
|
|
|
|
|
27
|
|
|
162
|
|
|
|
|
1365
|
|
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# Returns the validation errors, or undef if validation succeeded |
379
|
324
|
|
|
324
|
|
22668
|
sub err { $_[0][1] } |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# Returns the validated and normalized input, dies if validation didn't succeed. |
382
|
|
|
|
|
|
|
sub data { |
383
|
162
|
100
|
|
162
|
|
391
|
if($_[0][1]) { |
384
|
69
|
|
|
|
|
1014
|
require Data::Dumper; |
385
|
69
|
|
|
|
|
7278
|
my $s = Data::Dumper->new([$_[0][1]])->Terse(1)->Pair(':')->Indent(0)->Sortkeys(1)->Dump; |
386
|
69
|
|
|
|
|
10861
|
croak "Validation failed: $s"; |
387
|
|
|
|
|
|
|
} |
388
|
93
|
|
|
|
|
341
|
$_[0][0] |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Same as 'data', but returns partially validated and normalized data if validation failed. |
392
|
324
|
|
|
324
|
|
176204
|
sub unsafe_data { $_[0][0] } |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# TODO: Human-readable error message formatting |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
1; |