line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#====================================================================== |
2
|
|
|
|
|
|
|
package Data::Domain; # documentation at end of file |
3
|
|
|
|
|
|
|
#====================================================================== |
4
|
4
|
|
|
4
|
|
419974
|
use 5.010; |
|
4
|
|
|
|
|
41
|
|
5
|
4
|
|
|
4
|
|
23
|
use strict; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
90
|
|
6
|
4
|
|
|
4
|
|
18
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
102
|
|
7
|
4
|
|
|
4
|
|
30
|
use Carp; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
212
|
|
8
|
4
|
|
|
4
|
|
1811
|
use Data::Dumper; |
|
4
|
|
|
|
|
20133
|
|
|
4
|
|
|
|
|
212
|
|
9
|
4
|
|
|
4
|
|
1940
|
use Scalar::Does 0.007; |
|
4
|
|
|
|
|
473156
|
|
|
4
|
|
|
|
|
39
|
|
10
|
4
|
|
|
4
|
|
2302
|
use Scalar::Util (); |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
79
|
|
11
|
4
|
|
|
4
|
|
18
|
use Try::Tiny; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
236
|
|
12
|
4
|
|
|
4
|
|
2216
|
use List::MoreUtils qw/part natatime any/; |
|
4
|
|
|
|
|
39315
|
|
|
4
|
|
|
|
|
27
|
|
13
|
4
|
|
|
4
|
|
4942
|
use if $] < 5.037, experimental => 'smartmatch'; # smartmatch no longer experimental after 5.037 |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
31
|
|
14
|
4
|
50
|
|
|
|
47
|
use overload '""' => \&_stringify, |
15
|
4
|
|
|
4
|
|
16045
|
$] < 5.037 ? ('~~' => \&_matches) : (); # fully deprecated, so cannot be overloaded |
|
4
|
|
|
|
|
10
|
|
16
|
4
|
|
|
4
|
|
2154
|
use match::simple (); |
|
4
|
|
|
|
|
7300
|
|
|
4
|
|
|
|
|
493
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
our $VERSION = "1.11"; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
our $MESSAGE; # global var for last message from _matches() |
21
|
|
|
|
|
|
|
our $MAX_DEEP = 100; # limit for recursive calls to inspect() |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
24
|
|
|
|
|
|
|
# exports |
25
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# lists of symbols to export |
28
|
|
|
|
|
|
|
my @CONSTRUCTORS; |
29
|
|
|
|
|
|
|
my %SHORTCUTS; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
BEGIN { |
32
|
4
|
|
|
4
|
|
22
|
@CONSTRUCTORS = qw/Whatever Empty |
33
|
|
|
|
|
|
|
Num Int Nat Date Time String Handle |
34
|
|
|
|
|
|
|
Enum List Struct One_of All_of/; |
35
|
4
|
|
|
|
|
464
|
%SHORTCUTS = ( |
36
|
|
|
|
|
|
|
True => [ -true => 1 ], |
37
|
|
|
|
|
|
|
False => [ -true => 0 ], |
38
|
|
|
|
|
|
|
Defined => [ -defined => 1 ], |
39
|
|
|
|
|
|
|
Undef => [ -defined => 0 ], |
40
|
|
|
|
|
|
|
Blessed => [ -blessed => 1 ], |
41
|
|
|
|
|
|
|
Unblessed => [ -blessed => 0 ], |
42
|
|
|
|
|
|
|
Ref => [ -ref => 1 ], |
43
|
|
|
|
|
|
|
Unref => [ -ref => 0 ], |
44
|
|
|
|
|
|
|
Regexp => [ -does => 'Regexp' ], |
45
|
|
|
|
|
|
|
Obj => [ -blessed => 1 ], |
46
|
|
|
|
|
|
|
Class => [ -package => 1 ], |
47
|
|
|
|
|
|
|
); |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# setup exports through Sub::Exporter API |
51
|
|
|
|
|
|
|
use Sub::Exporter -setup => { |
52
|
|
|
|
|
|
|
exports => [ 'node_from_path', # no longer documented, but still present for backwards compat |
53
|
56
|
|
|
|
|
101
|
(map {$_ => \&_wrap_domain } @CONSTRUCTORS ), |
54
|
4
|
|
|
|
|
15
|
(map {$_ => \&_wrap_shortcut_options} keys %SHORTCUTS) ], |
|
44
|
|
|
|
|
127
|
|
55
|
|
|
|
|
|
|
groups => { constructors => \@CONSTRUCTORS, |
56
|
|
|
|
|
|
|
shortcuts => [keys %SHORTCUTS] }, |
57
|
|
|
|
|
|
|
collectors => { INIT => \&_sub_exporter_init }, |
58
|
|
|
|
|
|
|
installer => \&_sub_exporter_installer, |
59
|
4
|
|
|
4
|
|
2537
|
}; |
|
4
|
|
|
|
|
44050
|
|
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# customize Sub::Exporter to support "bang-syntax" for excluding symbols |
62
|
|
|
|
|
|
|
# see https://rt.cpan.org/Public/Bug/Display.html?id=80234 |
63
|
|
|
|
|
|
|
{ my @dont_export; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# detect symbols prefixed by '!' and remember them in @dont_export |
66
|
|
|
|
|
|
|
sub _sub_exporter_init { |
67
|
4
|
|
|
4
|
|
427
|
my ($collection, $context) = @_; |
68
|
4
|
|
|
|
|
8
|
my $args = $context->{import_args}; |
69
|
|
|
|
|
|
|
my ($exclude, $regular_args) |
70
|
4
|
100
|
66
|
5
|
|
76
|
= part {!ref $_->[0] && $_->[0] =~ /^!/ ? 0 : 1} @$args; |
|
5
|
|
|
|
|
63
|
|
71
|
4
|
|
|
|
|
20
|
@$args = @$regular_args; |
72
|
4
|
|
|
|
|
11
|
@dont_export = map {substr($_->[0], 1)} @$exclude; |
|
1
|
|
|
|
|
3
|
|
73
|
4
|
|
|
|
|
15
|
1; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
# install symbols, except those that belong to @dont_export |
77
|
|
|
|
|
|
|
sub _sub_exporter_installer { |
78
|
4
|
|
|
4
|
|
32
|
my ($arg, $to_export) = @_; |
79
|
4
|
|
|
|
|
57
|
my %export_hash = @$to_export; |
80
|
4
|
|
|
|
|
17
|
delete @export_hash{@dont_export}; |
81
|
4
|
|
|
|
|
88
|
Sub::Exporter::default_installer($arg, [%export_hash]); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# constructors group : for each domain constructor, we export a closure |
86
|
|
|
|
|
|
|
# that just calls new() on the corresponding subclass. For example, |
87
|
|
|
|
|
|
|
# Num(@args) is just equivalent to Data::Domain::Num->new(@args). |
88
|
|
|
|
|
|
|
sub _wrap_domain { |
89
|
56
|
|
|
56
|
|
3063
|
my ($class, $name, $args, $coll) = @_; |
90
|
56
|
|
|
131
|
|
256
|
return sub {return "Data::Domain::$name"->new(@_)}; |
|
131
|
|
|
|
|
63227
|
|
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# # shortcuts group : calling 'Whatever' with various pre-built options |
95
|
|
|
|
|
|
|
sub _wrap_shortcut_options { |
96
|
44
|
|
|
44
|
|
1471
|
my ($class, $name, $args, $coll) = @_; |
97
|
44
|
|
|
14
|
|
167
|
return sub {return Data::Domain::Whatever->new(@{$SHORTCUTS{$name}}, @_)}; |
|
14
|
|
|
|
|
6370
|
|
|
14
|
|
|
|
|
72
|
|
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
103
|
|
|
|
|
|
|
# messages |
104
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
my $builtin_msgs = { |
107
|
|
|
|
|
|
|
english => { |
108
|
|
|
|
|
|
|
Generic => { |
109
|
|
|
|
|
|
|
UNDEFINED => "undefined data", |
110
|
|
|
|
|
|
|
INVALID => "invalid", |
111
|
|
|
|
|
|
|
TOO_SMALL => "smaller than minimum '%s'", |
112
|
|
|
|
|
|
|
TOO_BIG => "bigger than maximum '%s'", |
113
|
|
|
|
|
|
|
EXCLUSION_SET => "belongs to exclusion set", |
114
|
|
|
|
|
|
|
MATCH_TRUE => "data true/false", |
115
|
|
|
|
|
|
|
MATCH_ISA => "is not a '%s'", |
116
|
|
|
|
|
|
|
MATCH_CAN => "does not have method '%s'", |
117
|
|
|
|
|
|
|
MATCH_DOES => "does not do '%s'", |
118
|
|
|
|
|
|
|
MATCH_BLESSED => "data blessed/unblessed", |
119
|
|
|
|
|
|
|
MATCH_PACKAGE => "data is/is not a package", |
120
|
|
|
|
|
|
|
MATCH_REF => "is/is not a reference", |
121
|
|
|
|
|
|
|
MATCH_SMART => "does not smart-match '%s'", |
122
|
|
|
|
|
|
|
MATCH_ISWEAK => "weak/strong reference", |
123
|
|
|
|
|
|
|
MATCH_READONLY=> "readonly data", |
124
|
|
|
|
|
|
|
MATCH_TAINTED => "tainted/untainted", |
125
|
|
|
|
|
|
|
}, |
126
|
|
|
|
|
|
|
Whatever => { |
127
|
|
|
|
|
|
|
MATCH_DEFINED => "data defined/undefined", |
128
|
|
|
|
|
|
|
}, |
129
|
|
|
|
|
|
|
Num => {INVALID => "invalid number",}, |
130
|
|
|
|
|
|
|
Date => {INVALID => "invalid date",}, |
131
|
|
|
|
|
|
|
String => { |
132
|
|
|
|
|
|
|
TOO_SHORT => "less than %d characters", |
133
|
|
|
|
|
|
|
TOO_LONG => "more than %d characters", |
134
|
|
|
|
|
|
|
SHOULD_MATCH => "should match '%s'", |
135
|
|
|
|
|
|
|
SHOULD_NOT_MATCH => "should not match '%s'", |
136
|
|
|
|
|
|
|
}, |
137
|
|
|
|
|
|
|
Handle => {INVALID => "is not an open filehandle"}, |
138
|
|
|
|
|
|
|
Enum => {NOT_IN_LIST => "not in enumeration list",}, |
139
|
|
|
|
|
|
|
List => { |
140
|
|
|
|
|
|
|
NOT_A_LIST => "is not an arrayref", |
141
|
|
|
|
|
|
|
TOO_SHORT => "less than %d items", |
142
|
|
|
|
|
|
|
TOO_LONG => "more than %d items", |
143
|
|
|
|
|
|
|
ANY => "should have at least one '%s'", |
144
|
|
|
|
|
|
|
}, |
145
|
|
|
|
|
|
|
Struct => { |
146
|
|
|
|
|
|
|
NOT_A_HASH => "is not a hashref", |
147
|
|
|
|
|
|
|
FORBIDDEN_FIELD => "contains forbidden field: '%s'" |
148
|
|
|
|
|
|
|
}, |
149
|
|
|
|
|
|
|
}, |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
"français" => { |
152
|
|
|
|
|
|
|
Generic => { |
153
|
|
|
|
|
|
|
UNDEFINED => "donnée non définie", |
154
|
|
|
|
|
|
|
INVALID => "incorrect", |
155
|
|
|
|
|
|
|
TOO_SMALL => "plus petit que le minimum '%s'", |
156
|
|
|
|
|
|
|
TOO_BIG => "plus grand que le maximum '%s'", |
157
|
|
|
|
|
|
|
EXCLUSION_SET => "fait partie des valeurs interdites", |
158
|
|
|
|
|
|
|
MATCH_TRUE => "donnée vraie/fausse", |
159
|
|
|
|
|
|
|
MATCH_ISA => "n'est pas un '%s'", |
160
|
|
|
|
|
|
|
MATCH_CAN => "n'a pas la méthode '%s'", |
161
|
|
|
|
|
|
|
MATCH_DOES => "ne se comporte pas comme un '%s'", |
162
|
|
|
|
|
|
|
MATCH_BLESSED => "donnée blessed/unblessed", |
163
|
|
|
|
|
|
|
MATCH_PACKAGE => "est/n'est pas un package", |
164
|
|
|
|
|
|
|
MATCH_REF => "est/n'est pas une référence", |
165
|
|
|
|
|
|
|
MATCH_SMART => "n'obéit pas au smart-match '%s'", |
166
|
|
|
|
|
|
|
MATCH_ISWEAK => "référence weak/strong", |
167
|
|
|
|
|
|
|
MATCH_READONLY=> "donnée readonly", |
168
|
|
|
|
|
|
|
MATCH_TAINTED => "tainted/untainted", |
169
|
|
|
|
|
|
|
}, |
170
|
|
|
|
|
|
|
Whatever => { |
171
|
|
|
|
|
|
|
MATCH_DEFINED => "donnée définie/non définie", |
172
|
|
|
|
|
|
|
}, |
173
|
|
|
|
|
|
|
Num => {INVALID => "nombre incorrect",}, |
174
|
|
|
|
|
|
|
Date => {INVALID => "date incorrecte",}, |
175
|
|
|
|
|
|
|
String => { |
176
|
|
|
|
|
|
|
TOO_SHORT => "moins de %d caractères", |
177
|
|
|
|
|
|
|
TOO_LONG => "plus de %d caractères", |
178
|
|
|
|
|
|
|
SHOULD_MATCH => "devrait être reconnu par la regex '%s'", |
179
|
|
|
|
|
|
|
SHOULD_NOT_MATCH => "ne devrait pas être reconnu par la regex '%s'", |
180
|
|
|
|
|
|
|
}, |
181
|
|
|
|
|
|
|
Handle => {INVALID => "n'est pas une filehandle ouverte"}, |
182
|
|
|
|
|
|
|
Enum => {NOT_IN_LIST => "n'appartient pas à la liste énumérée",}, |
183
|
|
|
|
|
|
|
List => { |
184
|
|
|
|
|
|
|
NOT_A_LIST => "n'est pas une arrayref", |
185
|
|
|
|
|
|
|
TOO_SHORT => "moins de %d éléments", |
186
|
|
|
|
|
|
|
TOO_LONG => "plus de %d éléments", |
187
|
|
|
|
|
|
|
ANY => "doit avoir au moins un '%s'", |
188
|
|
|
|
|
|
|
}, |
189
|
|
|
|
|
|
|
Struct => { |
190
|
|
|
|
|
|
|
NOT_A_HASH => "n'est pas une hashref", |
191
|
|
|
|
|
|
|
FORBIDDEN_FIELD => "contient le champ interdit: '%s'", |
192
|
|
|
|
|
|
|
}, |
193
|
|
|
|
|
|
|
}, |
194
|
|
|
|
|
|
|
}; |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# inherit Int and Nat messages from Num messages |
197
|
|
|
|
|
|
|
foreach my $language (keys %$builtin_msgs) { |
198
|
|
|
|
|
|
|
$builtin_msgs->{$language}{$_} = $builtin_msgs->{$language}{Num} |
199
|
|
|
|
|
|
|
for qw/Int Nat/; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# default messages : english |
203
|
|
|
|
|
|
|
my $global_msgs = $builtin_msgs->{english}; |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
206
|
|
|
|
|
|
|
# PUBLIC METHODS |
207
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub messages { # private class method |
210
|
3
|
|
|
3
|
1
|
7024
|
my ($class, $new_messages) = @_; |
211
|
3
|
50
|
33
|
|
|
20
|
croak "messages() is a class method in Data::Domain" |
212
|
|
|
|
|
|
|
if ref $class or $class ne 'Data::Domain'; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
$global_msgs = (ref $new_messages) ? $new_messages |
215
|
3
|
100
|
|
|
|
17
|
: $builtin_msgs->{$new_messages} |
|
|
50
|
|
|
|
|
|
216
|
|
|
|
|
|
|
or croak "no such builtin messages ($new_messages)"; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub inspect { |
221
|
982
|
|
|
982
|
1
|
3450
|
my ($self, $data, $context) = @_; |
222
|
4
|
|
|
4
|
|
7613
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
5564
|
|
223
|
|
|
|
|
|
|
|
224
|
982
|
100
|
|
|
|
1788
|
if (!defined $data) { |
225
|
|
|
|
|
|
|
# success if data was optional; |
226
|
33
|
100
|
|
|
|
80
|
return if $self->{-optional}; |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# only the 'Whatever' domain can accept undef; other domains will fail |
229
|
26
|
100
|
|
|
|
151
|
return $self->msg(UNDEFINED => '') |
230
|
|
|
|
|
|
|
unless $self->isa("Data::Domain::Whatever"); |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
else { # if $data is defined |
233
|
|
|
|
|
|
|
# check some general properties |
234
|
949
|
100
|
|
|
|
2455
|
if (my $isa = $self->{-isa}) { |
235
|
2
|
|
|
2
|
|
69
|
try {$data->isa($isa)} |
236
|
2
|
100
|
|
|
|
13
|
or return $self->msg(MATCH_ISA => $isa); |
237
|
|
|
|
|
|
|
} |
238
|
948
|
100
|
|
|
|
1784
|
if (my $role = $self->{-does}) { |
239
|
4
|
100
|
|
|
|
14
|
does($data, $role) |
240
|
|
|
|
|
|
|
or return $self->msg(MATCH_DOES => $role); |
241
|
|
|
|
|
|
|
} |
242
|
946
|
100
|
|
|
|
2521
|
if (my $can = $self->{-can}) { |
243
|
3
|
100
|
|
|
|
11
|
$can = [$can] unless does($can, 'ARRAY'); |
244
|
3
|
|
|
|
|
737
|
foreach my $method (@$can) { |
245
|
5
|
|
|
5
|
|
128
|
try {$data->can($method)} |
246
|
5
|
100
|
|
|
|
39
|
or return $self->msg(MATCH_CAN => $method); |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
} |
249
|
945
|
100
|
|
|
|
1697
|
if (my $match_target = $self->{-matches}) { |
250
|
2
|
100
|
|
|
|
12
|
match::simple::match($data, $match_target) |
251
|
|
|
|
|
|
|
or return $self->msg(MATCH_SMART => $match_target); |
252
|
|
|
|
|
|
|
} |
253
|
944
|
100
|
|
|
|
1528
|
if ($self->{-has}) { |
254
|
|
|
|
|
|
|
# EXPERIMENTAL: check methods results |
255
|
1
|
|
|
|
|
7
|
my @msgs = $self->_check_has($data, $context); |
256
|
1
|
50
|
|
|
|
8
|
return {HAS => \@msgs} if @msgs; |
257
|
|
|
|
|
|
|
} |
258
|
943
|
100
|
|
|
|
1632
|
if (defined $self->{-blessed}) { |
259
|
|
|
|
|
|
|
return $self->msg(MATCH_BLESSED => $self->{-blessed}) |
260
|
8
|
100
|
100
|
|
|
53
|
if Scalar::Util::blessed($data) xor $self->{-blessed}; |
261
|
|
|
|
|
|
|
} |
262
|
939
|
100
|
|
|
|
1593
|
if (defined $self->{-package}) { |
263
|
|
|
|
|
|
|
return $self->msg(MATCH_PACKAGE => $self->{-package}) |
264
|
3
|
100
|
100
|
|
|
35
|
if (!ref($data) && $data->isa($data)) xor $self->{-package}; |
|
|
|
50
|
|
|
|
|
265
|
|
|
|
|
|
|
} |
266
|
937
|
50
|
|
|
|
1517
|
if (defined $self->{-isweak}) { |
267
|
|
|
|
|
|
|
return $self->msg(MATCH_ISWEAK => $self->{-isweak}) |
268
|
0
|
0
|
0
|
|
|
0
|
if Scalar::Util::isweak($data) xor $self->{-isweak}; |
269
|
|
|
|
|
|
|
} |
270
|
937
|
50
|
|
|
|
1531
|
if (defined $self->{-readonly}) { |
271
|
|
|
|
|
|
|
return $self->msg(MATCH_READONLY => $self->{-readonly}) |
272
|
0
|
0
|
0
|
|
|
0
|
if Scalar::Util::readonly($data) xor $self->{-readonly}; |
273
|
|
|
|
|
|
|
} |
274
|
937
|
50
|
|
|
|
1673
|
if (defined $self->{-tainted}) { |
275
|
|
|
|
|
|
|
return $self->msg(MATCH_TAINTED => $self->{-tainted}) |
276
|
0
|
0
|
0
|
|
|
0
|
if Scalar::Util::readonly($data) xor $self->{-tainted}; |
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# properties that must be checked against both defined and undef data |
281
|
946
|
100
|
|
|
|
1614
|
if (defined $self->{-true}) { |
282
|
|
|
|
|
|
|
return $self->msg(MATCH_TRUE => $self->{-true}) |
283
|
11
|
100
|
100
|
|
|
56
|
if $data xor $self->{-true}; |
284
|
|
|
|
|
|
|
} |
285
|
941
|
100
|
|
|
|
1604
|
if (defined $self->{-ref}) { |
286
|
|
|
|
|
|
|
return $self->msg(MATCH_REF => $self->{-ref}) |
287
|
6
|
100
|
100
|
|
|
42
|
if ref $data xor $self->{-ref}; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# now call domain-specific _inspect() |
291
|
938
|
|
|
|
|
2448
|
return $self->_inspect($data, $context) |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub _check_has { |
296
|
1
|
|
|
1
|
|
3
|
my ($self, $data, $context) = @_; |
297
|
|
|
|
|
|
|
|
298
|
1
|
|
|
|
|
2
|
my @msgs; |
299
|
1
|
|
|
|
|
1
|
my $iterator = natatime 2, @{$self->{-has}}; |
|
1
|
|
|
|
|
13
|
|
300
|
1
|
|
|
|
|
9
|
while (my ($meth_to_call, $expectation) = $iterator->()) { |
301
|
3
|
100
|
|
|
|
23
|
my ($meth, @args) = does($meth_to_call, 'ARRAY') ? @$meth_to_call |
302
|
|
|
|
|
|
|
: ($meth_to_call); |
303
|
3
|
|
|
|
|
824
|
my $msg; |
304
|
3
|
50
|
|
|
|
9
|
if (does($expectation, 'ARRAY')) { |
305
|
0
|
|
|
0
|
|
0
|
$msg = try {my @result = $data->$meth(@args); |
306
|
0
|
|
|
|
|
0
|
my $domain = List(@$expectation); |
307
|
0
|
|
|
|
|
0
|
$domain->inspect(\@result)} |
308
|
0
|
|
|
0
|
|
0
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
else { |
311
|
3
|
|
|
3
|
|
158
|
$msg = try {my $result = $data->$meth(@args); |
312
|
2
|
|
|
|
|
47
|
$expectation->inspect($result)} |
313
|
3
|
|
|
1
|
|
285
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
1
|
|
|
|
|
23
|
|
|
1
|
|
|
|
|
5
|
|
314
|
|
|
|
|
|
|
} |
315
|
3
|
100
|
|
|
|
58
|
push @msgs, $meth_to_call => $msg if $msg; |
316
|
|
|
|
|
|
|
} |
317
|
1
|
|
|
|
|
8
|
return @msgs; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
sub _check_returns { |
323
|
0
|
|
|
0
|
|
0
|
my ($self, $data, $context) = @_; |
324
|
|
|
|
|
|
|
|
325
|
0
|
|
|
|
|
0
|
my @msgs; |
326
|
0
|
|
|
|
|
0
|
my $iterator = natatime 2, @{$self->{-returns}}; |
|
0
|
|
|
|
|
0
|
|
327
|
0
|
|
|
|
|
0
|
while (my ($args, $expectation) = $iterator->()) { |
328
|
0
|
|
|
|
|
0
|
my $msg; |
329
|
0
|
0
|
|
|
|
0
|
if (does($expectation, 'ARRAY')) { |
330
|
0
|
|
|
0
|
|
0
|
$msg = try {my @result = $data->(@$args); |
331
|
0
|
|
|
|
|
0
|
my $domain = List(@$expectation); |
332
|
0
|
|
|
|
|
0
|
$domain->inspect(\@result)} |
333
|
0
|
|
|
0
|
|
0
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
else { |
336
|
0
|
|
|
0
|
|
0
|
$msg = try {my $result = $data->(@$args); |
337
|
0
|
|
|
|
|
0
|
$expectation->inspect($result)} |
338
|
0
|
|
|
0
|
|
0
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
339
|
|
|
|
|
|
|
} |
340
|
0
|
0
|
|
|
|
0
|
push @msgs, $args => $msg if $msg; |
341
|
|
|
|
|
|
|
} |
342
|
0
|
|
|
|
|
0
|
return @msgs; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
349
|
|
|
|
|
|
|
# METHODS FOR INTERNAL USE |
350
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
sub msg { |
354
|
240
|
|
|
240
|
1
|
1868
|
my ($self, $msg_id, @args) = @_; |
355
|
240
|
|
|
|
|
452
|
my $msgs = $self->{-messages}; |
356
|
240
|
|
|
|
|
473
|
my $subclass = $self->subclass; |
357
|
240
|
|
66
|
|
|
809
|
my $name = $self->{-name} || $subclass; |
358
|
240
|
|
|
|
|
296
|
my $msg; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# perl v5.22 and above warns if there are too many @args for sprintf. |
361
|
|
|
|
|
|
|
# The line below prevents that warning |
362
|
4
|
|
|
4
|
|
31
|
no if $] ge '5.022000', warnings => 'redundant'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
32
|
|
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# if there is a user_defined message, return it |
365
|
240
|
100
|
|
|
|
476
|
if (defined $msgs) { |
366
|
11
|
|
|
|
|
26
|
for (ref $msgs) { |
367
|
11
|
100
|
|
|
|
61
|
/^CODE/ and return $msgs->($msg_id, @args); # user function |
368
|
10
|
100
|
|
|
|
69
|
/^$/ and return "$name: $msgs"; # user constant string |
369
|
2
|
50
|
|
|
|
8
|
/^HASH/ and do { $msg = $msgs->{$msg_id} # user hash of msgs |
|
2
|
50
|
|
|
|
22
|
|
370
|
|
|
|
|
|
|
and return sprintf "$name: $msg", @args; |
371
|
0
|
|
|
|
|
0
|
last; # not found in this hash - revert to $global_msgs |
372
|
|
|
|
|
|
|
}; |
373
|
0
|
|
|
|
|
0
|
croak "invalid -messages option"; # otherwise |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# otherwise, try global messages |
378
|
229
|
100
|
|
|
|
471
|
return $global_msgs->($msg_id, @args) if ref $global_msgs eq 'CODE'; |
379
|
|
|
|
|
|
|
$msg = $global_msgs->{$subclass}{$msg_id} # otherwise |
380
|
228
|
50
|
66
|
|
|
764
|
|| $global_msgs->{Generic}{$msg_id} |
381
|
|
|
|
|
|
|
or croak "no error string for message $msg_id"; |
382
|
|
|
|
|
|
|
|
383
|
228
|
|
|
|
|
1302
|
return sprintf "$name: $msg", @args; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
sub subclass { # returns the class name without initial 'Data::Domain::' |
388
|
367
|
|
|
367
|
1
|
546
|
my ($self) = @_; |
389
|
367
|
|
33
|
|
|
769
|
my $class = ref($self) || $self; |
390
|
367
|
|
|
|
|
1468
|
(my $subclass = $class) =~ s/^Data::Domain:://; |
391
|
367
|
|
|
|
|
1823
|
return $subclass; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub _expand_range { |
396
|
127
|
|
|
127
|
|
237
|
my ($self, $range_field, $min_field, $max_field) = @_; |
397
|
127
|
|
66
|
|
|
756
|
my $name = $self->{-name} || $self->subclass; |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# the range field will be replaced by min and max fields |
400
|
127
|
100
|
|
|
|
370
|
if (my $range = delete $self->{$range_field}) { |
401
|
13
|
|
|
|
|
25
|
for ($min_field, $max_field) { |
402
|
26
|
50
|
|
|
|
57
|
not defined $self->{$_} |
403
|
|
|
|
|
|
|
or croak "$name: incompatible options: $range_field / $_"; |
404
|
|
|
|
|
|
|
} |
405
|
13
|
50
|
33
|
|
|
38
|
does($range, 'ARRAY') and @$range == 2 |
406
|
|
|
|
|
|
|
or croak "$name: invalid argument for $range"; |
407
|
13
|
|
|
|
|
314
|
@{$self}{$min_field, $max_field} = @$range; |
|
13
|
|
|
|
|
43
|
|
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub _check_min_max { |
413
|
126
|
|
|
126
|
|
258
|
my ($self, $min_field, $max_field, $cmp_func) = @_; |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# choose the appropriate comparison function |
416
|
126
|
100
|
|
13
|
|
303
|
if ($cmp_func eq '<=') {$cmp_func = sub {$_[0] <= $_[1]}} |
|
88
|
100
|
|
|
|
285
|
|
|
13
|
50
|
|
|
|
53
|
|
417
|
25
|
|
|
4
|
|
86
|
elsif ($cmp_func eq 'le') {$cmp_func = sub {$_[0] le $_[1]}} |
|
4
|
|
|
|
|
16
|
|
418
|
|
|
|
|
|
|
elsif (does($cmp_func, 'CODE')) {} # already a coderef, do nothing |
419
|
0
|
|
|
|
|
0
|
else {croak "inappropriate cmp_func for _check_min_max"} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
# check that min is smaller than max |
422
|
126
|
|
|
|
|
1858
|
my ($min, $max) = @{$self}{$min_field, $max_field}; |
|
126
|
|
|
|
|
277
|
|
423
|
126
|
100
|
100
|
|
|
459
|
if (defined $min && defined $max) { |
424
|
21
|
100
|
|
|
|
42
|
$cmp_func->($min, $max) |
425
|
|
|
|
|
|
|
or croak $self->subclass . ": incompatible min/max values ($min/$max)"; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
sub _build_subdomain { |
431
|
469
|
|
|
469
|
|
827
|
my ($self, $domain, $context) = @_; |
432
|
4
|
|
|
4
|
|
3065
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
3240
|
|
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# avoid infinite loop |
435
|
469
|
100
|
|
|
|
630
|
@{$context->{path}} < $MAX_DEEP |
|
469
|
|
|
|
|
3930
|
|
436
|
|
|
|
|
|
|
or croak "inspect() deepness exceeded $MAX_DEEP; " |
437
|
|
|
|
|
|
|
. "modify \$Data::Domain::MAX_DEEP if you need more"; |
438
|
|
|
|
|
|
|
|
439
|
468
|
100
|
|
|
|
1147
|
if (does($domain, 'Data::Domain')) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# already a domain, nothing to do |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
elsif (does($domain, 'CODE')) { |
443
|
|
|
|
|
|
|
# this is a lazy domain, need to call the coderef to get a real domain |
444
|
230
|
|
|
230
|
|
9105
|
$domain = try {$domain->($context)} |
445
|
|
|
|
|
|
|
catch { # remove "at source_file, line ..." from error message |
446
|
1
|
|
|
1
|
|
251
|
(my $error_msg = $_) =~ s/\bat\b.*//s; |
447
|
|
|
|
|
|
|
# return an empty domain that reports the error message |
448
|
1
|
|
|
|
|
8
|
Data::Domain::Empty->new(-name => "domain parameters", |
449
|
|
|
|
|
|
|
-messages => $error_msg); |
450
|
230
|
|
|
|
|
8035
|
}; |
451
|
|
|
|
|
|
|
# did we really get a domain ? |
452
|
230
|
50
|
|
|
|
3214
|
does($domain, "Data::Domain") |
453
|
|
|
|
|
|
|
or croak "lazy domain coderef returned an invalid domain"; |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
elsif (!ref $domain) { |
456
|
|
|
|
|
|
|
# this is a scalar, build a constant domain with that single value |
457
|
6
|
100
|
|
|
|
271
|
my $subclass = Scalar::Util::looks_like_number($domain) ? 'Num' : 'String'; |
458
|
6
|
|
|
|
|
28
|
$domain = "Data::Domain::$subclass"->new(-min => $domain, |
459
|
|
|
|
|
|
|
-max => $domain, |
460
|
|
|
|
|
|
|
-name => "constant $subclass"); |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
else { |
463
|
0
|
|
|
|
|
0
|
croak "unknown subdomain : $domain"; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
468
|
|
|
|
|
8073
|
return $domain; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
471
|
|
|
|
|
|
|
# UTILITY FUNCTIONS (NOT METHODS) |
472
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# valid options for all subclasses |
475
|
|
|
|
|
|
|
my @common_options = qw/-optional -name -messages |
476
|
|
|
|
|
|
|
-true -isa -can -does -matches -ref |
477
|
|
|
|
|
|
|
-has -returns |
478
|
|
|
|
|
|
|
-blessed -package -isweak -readonly -tainted/; |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
sub _parse_args { |
481
|
153
|
|
|
153
|
|
308
|
my ($args_ref, $options_ref, $default_option, $arg_type) = @_; |
482
|
|
|
|
|
|
|
|
483
|
153
|
|
|
|
|
208
|
my %parsed; |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# parse named arguments |
486
|
153
|
|
100
|
|
|
780
|
while (@$args_ref and $args_ref->[0] =~ /^-/) { |
487
|
123
|
50
|
|
518
|
|
574
|
any {$args_ref->[0] eq $_} (@$options_ref, @common_options) |
|
518
|
|
|
|
|
822
|
|
488
|
|
|
|
|
|
|
or croak "invalid argument: $args_ref->[0]"; |
489
|
123
|
|
|
|
|
411
|
my ($key, $val) = (shift @$args_ref, shift @$args_ref); |
490
|
123
|
|
|
|
|
464
|
$parsed{$key} = $val; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
# remaining arguments are mapped to the default option |
494
|
153
|
100
|
|
|
|
492
|
if (@$args_ref) { |
495
|
24
|
50
|
|
|
|
49
|
$default_option or croak "too many args to new()"; |
496
|
24
|
50
|
|
|
|
52
|
not exists $parsed{$default_option} |
497
|
|
|
|
|
|
|
or croak "can't have default args if $default_option is set"; |
498
|
24
|
50
|
|
|
|
82
|
$parsed{$default_option} |
|
|
100
|
|
|
|
|
|
499
|
|
|
|
|
|
|
= $arg_type eq 'scalar' ? $args_ref->[0] |
500
|
|
|
|
|
|
|
: $arg_type eq 'arrayref' ? $args_ref |
501
|
|
|
|
|
|
|
: croak "unknown type for default option: $arg_type"; |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
153
|
|
|
|
|
296
|
return \%parsed; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
sub node_from_path { # no longer documented, but still present for backwards compat |
509
|
0
|
|
|
0
|
1
|
0
|
my ($root, $path0, @path) = @_; |
510
|
0
|
0
|
|
|
|
0
|
return $root if not defined $path0; |
511
|
0
|
0
|
|
|
|
0
|
return undef if not defined $root; |
512
|
0
|
0
|
|
|
|
0
|
return node_from_path($root->{$path0}, @path) |
513
|
|
|
|
|
|
|
if does($root, 'HASH'); |
514
|
0
|
0
|
|
|
|
0
|
return node_from_path($root->[$path0], @path) |
515
|
|
|
|
|
|
|
if does($root, 'ARRAY'); |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
# otherwise |
518
|
0
|
|
|
|
|
0
|
croak "node_from_path: incorrect root/path"; |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
522
|
|
|
|
|
|
|
# implementation for overloaded operators |
523
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
524
|
|
|
|
|
|
|
sub _matches { |
525
|
2
|
|
|
2
|
|
1183
|
my ($self, $data, $call_order) = @_; |
526
|
2
|
|
|
|
|
6
|
$Data::Domain::MESSAGE = $self->inspect($data); |
527
|
2
|
|
|
|
|
10
|
return !$Data::Domain::MESSAGE; # smart match successful if no error message |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub _stringify { |
531
|
217
|
|
|
217
|
|
2588
|
my ($self) = @_; |
532
|
217
|
|
|
|
|
567
|
my $dumper = Data::Dumper->new([$self])->Indent(0)->Terse(1); |
533
|
217
|
|
|
|
|
7180
|
return $dumper->Dump; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
#====================================================================== |
539
|
|
|
|
|
|
|
package Data::Domain::Whatever; |
540
|
|
|
|
|
|
|
#====================================================================== |
541
|
4
|
|
|
4
|
|
33
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
136
|
|
542
|
4
|
|
|
4
|
|
25
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
107
|
|
543
|
4
|
|
|
4
|
|
22
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
250
|
|
544
|
4
|
|
|
4
|
|
78
|
use Scalar::Does qw/does/; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
31
|
|
545
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
sub new { |
548
|
26
|
|
|
26
|
|
52
|
my $class = shift; |
549
|
26
|
|
|
|
|
50
|
my @options = qw/-defined/; |
550
|
26
|
|
|
|
|
62
|
my $self = Data::Domain::_parse_args( \@_, \@options ); |
551
|
26
|
|
|
|
|
51
|
bless $self, $class; |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
not ($self->{-defined } && $self->{-optional}) |
554
|
26
|
50
|
66
|
|
|
136
|
or croak "both -defined and -optional: meaningless!"; |
555
|
|
|
|
|
|
|
|
556
|
26
|
|
|
|
|
96
|
return $self; |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
sub _inspect { |
560
|
32
|
|
|
32
|
|
63
|
my ($self, $data) = @_; |
561
|
|
|
|
|
|
|
|
562
|
32
|
100
|
|
|
|
69
|
if (defined $self->{-defined}) { |
563
|
|
|
|
|
|
|
return $self->msg(MATCH_DEFINED => $self->{-defined}) |
564
|
9
|
100
|
100
|
|
|
59
|
if defined($data) xor $self->{-defined}; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
# otherwise, success |
568
|
27
|
|
|
|
|
119
|
return; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
#====================================================================== |
573
|
|
|
|
|
|
|
package Data::Domain::Empty; |
574
|
|
|
|
|
|
|
#====================================================================== |
575
|
4
|
|
|
4
|
|
3055
|
use strict; |
|
4
|
|
|
|
|
30
|
|
|
4
|
|
|
|
|
82
|
|
576
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
102
|
|
577
|
4
|
|
|
4
|
|
19
|
use Carp; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
550
|
|
578
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
sub new { |
581
|
2
|
|
|
2
|
|
5
|
my $class = shift; |
582
|
2
|
|
|
|
|
7
|
my @options = (); |
583
|
2
|
|
|
|
|
8
|
my $self = Data::Domain::_parse_args( \@_, \@options ); |
584
|
2
|
|
|
|
|
12
|
bless $self, $class; |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
sub _inspect { |
588
|
5
|
|
|
5
|
|
11
|
my ($self, $data) = @_; |
589
|
|
|
|
|
|
|
|
590
|
5
|
|
|
|
|
16
|
return $self->msg(INVALID => ''); # always fails |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
#====================================================================== |
595
|
|
|
|
|
|
|
package Data::Domain::Num; |
596
|
|
|
|
|
|
|
#====================================================================== |
597
|
4
|
|
|
4
|
|
26
|
use strict; |
|
4
|
|
|
|
|
57
|
|
|
4
|
|
|
|
|
82
|
|
598
|
4
|
|
|
4
|
|
17
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
82
|
|
599
|
4
|
|
|
4
|
|
19
|
use Carp; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
183
|
|
600
|
4
|
|
|
4
|
|
22
|
use Scalar::Util qw/looks_like_number/; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
141
|
|
601
|
4
|
|
|
4
|
|
40
|
use Try::Tiny; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
1516
|
|
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
sub new { |
606
|
50
|
|
|
50
|
|
81
|
my $class = shift; |
607
|
50
|
|
|
|
|
118
|
my @options = qw/-range -min -max -not_in/; |
608
|
50
|
|
|
|
|
116
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
609
|
50
|
|
|
|
|
93
|
bless $self, $class; |
610
|
|
|
|
|
|
|
|
611
|
50
|
|
|
|
|
142
|
$self->_expand_range(qw/-range -min -max/); |
612
|
50
|
|
|
|
|
133
|
$self->_check_min_max(qw/-min -max <=/); |
613
|
|
|
|
|
|
|
|
614
|
49
|
100
|
|
|
|
112
|
if ($self->{-not_in}) { |
615
|
1
|
|
|
1
|
|
28
|
try {my $vals = $self->{-not_in}; |
616
|
1
|
50
|
|
|
|
6
|
@$vals > 0 and not grep {!looks_like_number($_)} @$vals} |
|
2
|
|
|
|
|
9
|
|
617
|
1
|
50
|
|
|
|
6
|
or croak "-not_in : needs an arrayref of numbers"; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
49
|
|
|
|
|
278
|
return $self; |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
sub _inspect { |
624
|
294
|
|
|
294
|
|
500
|
my ($self, $data) = @_; |
625
|
|
|
|
|
|
|
|
626
|
294
|
100
|
|
|
|
881
|
looks_like_number($data) |
627
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
628
|
|
|
|
|
|
|
|
629
|
187
|
100
|
|
|
|
421
|
if (defined $self->{-min}) { |
630
|
|
|
|
|
|
|
$data >= $self->{-min} |
631
|
31
|
100
|
|
|
|
74
|
or return $self->msg(TOO_SMALL => $self->{-min}); |
632
|
|
|
|
|
|
|
} |
633
|
181
|
100
|
|
|
|
358
|
if (defined $self->{-max}) { |
634
|
|
|
|
|
|
|
$data <= $self->{-max} |
635
|
12
|
100
|
|
|
|
39
|
or return $self->msg(TOO_BIG => $self->{-max}); |
636
|
|
|
|
|
|
|
} |
637
|
177
|
100
|
|
|
|
344
|
if (defined $self->{-not_in}) { |
638
|
5
|
100
|
|
|
|
7
|
grep {$data == $_} @{$self->{-not_in}} |
|
10
|
|
|
|
|
32
|
|
|
5
|
|
|
|
|
13
|
|
639
|
|
|
|
|
|
|
and return $self->msg(EXCLUSION_SET => $data); |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
175
|
|
|
|
|
545
|
return; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
#====================================================================== |
647
|
|
|
|
|
|
|
package Data::Domain::Int; |
648
|
|
|
|
|
|
|
#====================================================================== |
649
|
4
|
|
|
4
|
|
28
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
95
|
|
650
|
4
|
|
|
4
|
|
26
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
587
|
|
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
our @ISA = 'Data::Domain::Num'; |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
sub _inspect { |
655
|
76
|
|
|
76
|
|
137
|
my ($self, $data) = @_; |
656
|
|
|
|
|
|
|
|
657
|
76
|
100
|
66
|
|
|
529
|
defined($data) and $data =~ /^-?\d+$/ |
658
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
659
|
56
|
|
|
|
|
213
|
return $self->SUPER::_inspect($data); |
660
|
|
|
|
|
|
|
} |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
#====================================================================== |
664
|
|
|
|
|
|
|
package Data::Domain::Nat; |
665
|
|
|
|
|
|
|
#====================================================================== |
666
|
4
|
|
|
4
|
|
28
|
use strict; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
111
|
|
667
|
4
|
|
|
4
|
|
21
|
use warnings; |
|
4
|
|
|
|
|
28
|
|
|
4
|
|
|
|
|
523
|
|
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
our @ISA = 'Data::Domain::Num'; |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
sub _inspect { |
672
|
3
|
|
|
3
|
|
5
|
my ($self, $data) = @_; |
673
|
|
|
|
|
|
|
|
674
|
3
|
100
|
66
|
|
|
33
|
defined($data) and $data =~ /^\d+$/ |
675
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
676
|
2
|
|
|
|
|
7
|
return $self->SUPER::_inspect($data); |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
#====================================================================== |
681
|
|
|
|
|
|
|
package Data::Domain::String; |
682
|
|
|
|
|
|
|
#====================================================================== |
683
|
4
|
|
|
4
|
|
27
|
use strict; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
106
|
|
684
|
4
|
|
|
4
|
|
23
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
112
|
|
685
|
4
|
|
|
4
|
|
20
|
use Carp; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
1947
|
|
686
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
sub new { |
689
|
25
|
|
|
25
|
|
54
|
my $class = shift; |
690
|
25
|
|
|
|
|
71
|
my @options = qw/-regex -antiregex |
691
|
|
|
|
|
|
|
-range -min -max |
692
|
|
|
|
|
|
|
-length -min_length -max_length |
693
|
|
|
|
|
|
|
-not_in/; |
694
|
25
|
|
|
|
|
70
|
my $self = Data::Domain::_parse_args(\@_, \@options, -regex => 'scalar'); |
695
|
25
|
|
|
|
|
54
|
bless $self, $class; |
696
|
|
|
|
|
|
|
|
697
|
25
|
|
|
|
|
76
|
$self->_expand_range(qw/-range -min -max/); |
698
|
25
|
|
|
|
|
68
|
$self->_check_min_max(qw/-min -max le/); |
699
|
|
|
|
|
|
|
|
700
|
25
|
|
|
|
|
68
|
$self->_expand_range(qw/-length -min_length -max_length/); |
701
|
25
|
|
|
|
|
60
|
$self->_check_min_max(qw/-min_length -max_length <=/); |
702
|
|
|
|
|
|
|
|
703
|
24
|
|
|
|
|
143
|
return $self; |
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
sub _inspect { |
707
|
162
|
|
|
162
|
|
271
|
my ($self, $data) = @_; |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
# $data must be Unref or obj with a stringification method |
710
|
162
|
100
|
100
|
|
|
380
|
!ref($data) || overload::Method($data, '""') |
711
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
712
|
159
|
100
|
|
|
|
340
|
if ($self->{-min_length}) { |
713
|
|
|
|
|
|
|
length($data) >= $self->{-min_length} |
714
|
6
|
100
|
|
|
|
19
|
or return $self->msg(TOO_SHORT => $self->{-min_length}); |
715
|
|
|
|
|
|
|
} |
716
|
158
|
100
|
|
|
|
300
|
if (defined $self->{-max_length}) { |
717
|
|
|
|
|
|
|
length($data) <= $self->{-max_length} |
718
|
5
|
100
|
|
|
|
17
|
or return $self->msg(TOO_LONG => $self->{-max_length}); |
719
|
|
|
|
|
|
|
} |
720
|
155
|
100
|
|
|
|
319
|
if ($self->{-regex}) { |
721
|
|
|
|
|
|
|
$data =~ $self->{-regex} |
722
|
132
|
100
|
|
|
|
815
|
or return $self->msg(SHOULD_MATCH => $self->{-regex}); |
723
|
|
|
|
|
|
|
} |
724
|
142
|
100
|
|
|
|
308
|
if ($self->{-antiregex}) { |
725
|
|
|
|
|
|
|
$data !~ $self->{-antiregex} |
726
|
2
|
100
|
|
|
|
20
|
or return $self->msg(SHOULD_NOT_MATCH => $self->{-antiregex}); |
727
|
|
|
|
|
|
|
} |
728
|
141
|
100
|
|
|
|
249
|
if (defined $self->{-min}) { |
729
|
|
|
|
|
|
|
$data ge $self->{-min} |
730
|
4
|
100
|
|
|
|
13
|
or return $self->msg(TOO_SMALL => $self->{-min}); |
731
|
|
|
|
|
|
|
} |
732
|
140
|
100
|
|
|
|
255
|
if (defined $self->{-max}) { |
733
|
|
|
|
|
|
|
$data le $self->{-max} |
734
|
3
|
100
|
|
|
|
10
|
or return $self->msg(TOO_BIG => $self->{-max}); |
735
|
|
|
|
|
|
|
} |
736
|
139
|
100
|
|
|
|
279
|
if ($self->{-not_in}) { |
737
|
1
|
50
|
|
|
|
3
|
grep {$data eq $_} @{$self->{-not_in}} |
|
2
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
4
|
|
738
|
|
|
|
|
|
|
and return $self->msg(EXCLUSION_SET => $data); |
739
|
|
|
|
|
|
|
} |
740
|
|
|
|
|
|
|
|
741
|
139
|
|
|
|
|
309
|
return; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
#====================================================================== |
746
|
|
|
|
|
|
|
package Data::Domain::Date; |
747
|
|
|
|
|
|
|
#====================================================================== |
748
|
4
|
|
|
4
|
|
26
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
93
|
|
749
|
4
|
|
|
4
|
|
27
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
105
|
|
750
|
4
|
|
|
4
|
|
19
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
218
|
|
751
|
4
|
|
|
4
|
|
22
|
use Try::Tiny; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
310
|
|
752
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
|
755
|
4
|
|
|
|
|
25
|
use autouse 'Date::Calc' => qw/Decode_Date_EU Decode_Date_US Date_to_Text |
756
|
4
|
|
|
4
|
|
1899
|
Delta_Days Add_Delta_Days Today check_date/; |
|
4
|
|
|
|
|
2985
|
|
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
my $date_parser = \&Decode_Date_EU; |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
761
|
|
|
|
|
|
|
# utility functions |
762
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
763
|
|
|
|
|
|
|
sub _print_date { |
764
|
3
|
|
|
3
|
|
6
|
my $date = shift; |
765
|
3
|
|
|
|
|
8
|
$date = _expand_dynamic_date($date); |
766
|
3
|
|
|
|
|
14
|
return Date_to_Text(@$date); |
767
|
|
|
|
|
|
|
} |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
my $dynamic_date = qr/^(today|yesterday|tomorrow)$/; |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
sub _expand_dynamic_date { |
773
|
42
|
|
|
42
|
|
56
|
my $date = shift; |
774
|
42
|
100
|
|
|
|
82
|
if (not ref $date) { |
775
|
|
|
|
|
|
|
$date = { |
776
|
|
|
|
|
|
|
today => [Today], |
777
|
|
|
|
|
|
|
yesterday => [Add_Delta_Days(Today, -1)], |
778
|
|
|
|
|
|
|
tomorrow => [Add_Delta_Days(Today, +1)] |
779
|
7
|
50
|
|
|
|
280
|
}->{$date} or croak "unexpected date : $date"; |
780
|
|
|
|
|
|
|
} |
781
|
42
|
|
|
|
|
215
|
return $date; |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
sub _date_cmp { |
785
|
15
|
|
|
15
|
|
30
|
my ($d1, $d2) = map {_expand_dynamic_date($_)} @_; |
|
30
|
|
|
|
|
45
|
|
786
|
15
|
|
|
|
|
79
|
return -Delta_Days(@$d1, @$d2); |
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
791
|
|
|
|
|
|
|
# public API |
792
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
sub parser { |
795
|
1
|
|
|
1
|
|
440
|
my ($class, $new_parser) = @_; |
796
|
1
|
50
|
|
|
|
4
|
not ref $class or croak "Data::Domain::Date::parser is a class method"; |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
$date_parser = |
799
|
|
|
|
|
|
|
(ref $new_parser eq 'CODE') |
800
|
|
|
|
|
|
|
? $new_parser |
801
|
|
|
|
|
|
|
: {US => \&Decode_Date_US, |
802
|
1
|
50
|
|
|
|
12
|
EU => \&Decode_Date_EU}->{$new_parser} |
|
|
50
|
|
|
|
|
|
803
|
|
|
|
|
|
|
or croak "unknown date parser : $new_parser"; |
804
|
1
|
|
|
|
|
3
|
return $date_parser; |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
sub new { |
809
|
11
|
|
|
11
|
|
3621
|
my $class = shift; |
810
|
11
|
|
|
|
|
30
|
my @options = qw/-range -min -max -not_in/; |
811
|
11
|
|
|
|
|
29
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
812
|
11
|
|
|
|
|
23
|
bless $self, $class; |
813
|
|
|
|
|
|
|
|
814
|
11
|
|
|
|
|
54
|
$self->_expand_range(qw/-range -min -max/); |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
# parse date boundaries into internal representation (arrayrefs) |
817
|
11
|
|
|
|
|
23
|
for my $bound (qw/-min -max/) { |
818
|
21
|
100
|
100
|
|
|
97
|
if ($self->{$bound} and $self->{$bound} !~ $dynamic_date) { |
819
|
6
|
100
|
|
|
|
22
|
my @date = $date_parser->($self->{$bound}) |
820
|
|
|
|
|
|
|
or croak "invalid date ($bound): $self->{$bound}"; |
821
|
5
|
|
|
|
|
84
|
$self->{$bound} = \@date; |
822
|
|
|
|
|
|
|
} |
823
|
|
|
|
|
|
|
} |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
# check order of boundaries |
826
|
10
|
|
|
2
|
|
50
|
$self->_check_min_max(qw/-min -max/, sub {_date_cmp($_[0], $_[1]) <= 0}); |
|
2
|
|
|
|
|
7
|
|
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
# parse dates in the exclusion set into internal representation |
829
|
9
|
100
|
|
|
|
95
|
if ($self->{-not_in}) { |
830
|
1
|
|
|
|
|
3
|
my @excl_dates; |
831
|
|
|
|
|
|
|
try { |
832
|
1
|
|
|
1
|
|
28
|
foreach my $date (@{$self->{-not_in}}) { |
|
1
|
|
|
|
|
4
|
|
833
|
2
|
100
|
|
|
|
13
|
if ($date =~ $dynamic_date) { |
834
|
1
|
|
|
|
|
3
|
push @excl_dates, $date; |
835
|
|
|
|
|
|
|
} |
836
|
|
|
|
|
|
|
else { |
837
|
1
|
50
|
|
|
|
4
|
my @parsed_date = $date_parser->($date) or die "wrong date"; |
838
|
1
|
|
|
|
|
17
|
push @excl_dates, \@parsed_date; |
839
|
|
|
|
|
|
|
} |
840
|
|
|
|
|
|
|
} |
841
|
1
|
|
|
|
|
5
|
@excl_dates > 0; |
842
|
|
|
|
|
|
|
} |
843
|
1
|
50
|
|
|
|
6
|
or croak "-not_in : needs an arrayref of dates"; |
844
|
1
|
|
|
|
|
18
|
$self->{-not_in} = \@excl_dates; |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
|
847
|
9
|
|
|
|
|
50
|
return $self; |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
sub _inspect { |
852
|
18
|
|
|
18
|
|
39
|
my ($self, $data) = @_; |
853
|
|
|
|
|
|
|
|
854
|
18
|
|
|
18
|
|
85
|
my @date = try {$date_parser->($data)}; |
|
18
|
|
|
|
|
483
|
|
855
|
18
|
100
|
66
|
|
|
12925
|
@date && check_date(@date) |
856
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
857
|
|
|
|
|
|
|
|
858
|
14
|
100
|
|
|
|
155
|
if (defined $self->{-min}) { |
859
|
6
|
|
|
|
|
16
|
my $min = _expand_dynamic_date($self->{-min}); |
860
|
|
|
|
|
|
|
!check_date(@$min) || (_date_cmp(\@date, $min) < 0) |
861
|
6
|
100
|
66
|
|
|
26
|
and return $self->msg(TOO_SMALL => _print_date($self->{-min})); |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
|
864
|
12
|
100
|
|
|
|
79
|
if (defined $self->{-max}) { |
865
|
3
|
|
|
|
|
8
|
my $max = _expand_dynamic_date($self->{-max}); |
866
|
|
|
|
|
|
|
!check_date(@$max) || (_date_cmp(\@date, $max) > 0) |
867
|
3
|
100
|
66
|
|
|
16
|
and return $self->msg(TOO_BIG => _print_date($self->{-max})); |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
|
870
|
11
|
100
|
|
|
|
29
|
if ($self->{-not_in}) { |
871
|
2
|
100
|
|
|
|
3
|
grep {_date_cmp(\@date, $_) == 0} @{$self->{-not_in}} |
|
4
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
6
|
|
872
|
|
|
|
|
|
|
and return $self->msg(EXCLUSION_SET => $data); |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
|
875
|
10
|
|
|
|
|
38
|
return; |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
#====================================================================== |
880
|
|
|
|
|
|
|
package Data::Domain::Time; |
881
|
|
|
|
|
|
|
#====================================================================== |
882
|
4
|
|
|
4
|
|
4698
|
use strict; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
137
|
|
883
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
131
|
|
884
|
4
|
|
|
4
|
|
23
|
use Carp; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
3049
|
|
885
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
my $time_regex = qr/^(\d\d?):?(\d\d?)?:?(\d\d?)?$/; |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
sub _valid_time { |
890
|
9
|
|
|
9
|
|
21
|
my ($h, $m, $s) = @_; |
891
|
9
|
|
50
|
|
|
17
|
$m ||= 0; |
892
|
9
|
|
50
|
|
|
35
|
$s ||= 0; |
893
|
9
|
|
66
|
|
|
56
|
return ($h <= 23 && $m <= 59 && $s <= 59); |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
sub _expand_dynamic_time { |
898
|
16
|
|
|
16
|
|
21
|
my $time = shift; |
899
|
16
|
50
|
|
|
|
31
|
if (not ref $time) { |
900
|
0
|
0
|
|
|
|
0
|
$time eq 'now' or croak "unexpected time : $time"; |
901
|
0
|
|
|
|
|
0
|
$time = [(localtime)[2, 1, 0]]; |
902
|
|
|
|
|
|
|
} |
903
|
16
|
|
|
|
|
31
|
return $time; |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
sub _time_cmp { |
908
|
7
|
|
|
7
|
|
14
|
my ($t1, $t2) = map {_expand_dynamic_time($_)} @_; |
|
14
|
|
|
|
|
20
|
|
909
|
|
|
|
|
|
|
|
910
|
7
|
|
33
|
|
|
40
|
return $t1->[0] <=> $t2->[0] # hours |
911
|
|
|
|
|
|
|
|| ($t1->[1] || 0) <=> ($t2->[1] || 0) # minutes |
912
|
|
|
|
|
|
|
|| ($t1->[2] || 0) <=> ($t2->[2] || 0); # seconds |
913
|
|
|
|
|
|
|
} |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
sub _print_time { |
916
|
2
|
|
|
2
|
|
5
|
my $time = _expand_dynamic_time(shift); |
917
|
2
|
100
|
|
|
|
4
|
return sprintf "%02d:%02d:%02d", map {$_ || 0} @$time; |
|
6
|
|
|
|
|
26
|
|
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
sub new { |
922
|
3
|
|
|
3
|
|
8
|
my $class = shift; |
923
|
3
|
|
|
|
|
9
|
my @options = qw/-range -min -max/; |
924
|
3
|
|
|
|
|
8
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
925
|
3
|
|
|
|
|
7
|
bless $self, $class; |
926
|
|
|
|
|
|
|
|
927
|
3
|
|
|
|
|
11
|
$self->_expand_range(qw/-range -min -max/); |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
# parse time boundaries |
930
|
3
|
|
|
|
|
7
|
for my $bound (qw/-min -max/) { |
931
|
6
|
100
|
66
|
|
|
25
|
if ($self->{$bound} and $self->{$bound} ne 'now') { |
932
|
4
|
|
|
|
|
42
|
my @time = ($self->{$bound} =~ $time_regex); |
933
|
4
|
50
|
33
|
|
|
15
|
@time && _valid_time(@time) |
934
|
|
|
|
|
|
|
or croak "invalid time ($bound): $self->{$bound}"; |
935
|
4
|
|
|
|
|
12
|
$self->{$bound} = \@time; |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
} |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
# check order of boundaries |
940
|
3
|
|
|
2
|
|
18
|
$self->_check_min_max(qw/-min -max/, sub {_time_cmp($_[0], $_[1]) <= 0}); |
|
2
|
|
|
|
|
7
|
|
941
|
|
|
|
|
|
|
|
942
|
2
|
|
|
|
|
18
|
return $self; |
943
|
|
|
|
|
|
|
} |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
sub _inspect { |
947
|
6
|
|
|
6
|
|
11
|
my ($self, $data) = @_; |
948
|
|
|
|
|
|
|
|
949
|
6
|
|
|
|
|
51
|
my @t = ($data =~ $time_regex); |
950
|
6
|
100
|
100
|
|
|
29
|
@t and _valid_time(@t) |
951
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
952
|
|
|
|
|
|
|
|
953
|
4
|
100
|
|
|
|
14
|
if (defined $self->{-min}) { |
954
|
|
|
|
|
|
|
_time_cmp(\@t, $self->{-min}) < 0 |
955
|
3
|
100
|
|
|
|
11
|
and return $self->msg(TOO_SMALL => _print_time($self->{-min})); |
956
|
|
|
|
|
|
|
} |
957
|
|
|
|
|
|
|
|
958
|
3
|
100
|
|
|
|
12
|
if (defined $self->{-max}) { |
959
|
|
|
|
|
|
|
_time_cmp(\@t, $self->{-max}) > 0 |
960
|
2
|
100
|
|
|
|
4
|
and return $self->msg(TOO_BIG => _print_time($self->{-max})); |
961
|
|
|
|
|
|
|
} |
962
|
|
|
|
|
|
|
|
963
|
2
|
|
|
|
|
10
|
return; |
964
|
|
|
|
|
|
|
} |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
#====================================================================== |
969
|
|
|
|
|
|
|
package Data::Domain::Handle; |
970
|
|
|
|
|
|
|
#====================================================================== |
971
|
4
|
|
|
4
|
|
31
|
use strict; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
100
|
|
972
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
125
|
|
973
|
4
|
|
|
4
|
|
22
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
798
|
|
974
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
sub new { |
977
|
1
|
|
|
1
|
|
3
|
my $class = shift; |
978
|
1
|
|
|
|
|
4
|
my @options = (); |
979
|
1
|
|
|
|
|
3
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
980
|
1
|
|
|
|
|
4
|
bless $self, $class; |
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
sub _inspect { |
984
|
3
|
|
|
3
|
|
8
|
my ($self, $data) = @_; |
985
|
3
|
100
|
|
|
|
18
|
Scalar::Util::openhandle($data) |
986
|
|
|
|
|
|
|
or return $self->msg(INVALID => ''); |
987
|
|
|
|
|
|
|
|
988
|
2
|
|
|
|
|
9
|
return; # otherwise OK, no error |
989
|
|
|
|
|
|
|
} |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
#====================================================================== |
995
|
|
|
|
|
|
|
package Data::Domain::Enum; |
996
|
|
|
|
|
|
|
#====================================================================== |
997
|
4
|
|
|
4
|
|
29
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
101
|
|
998
|
4
|
|
|
4
|
|
23
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
123
|
|
999
|
4
|
|
|
4
|
|
22
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
229
|
|
1000
|
4
|
|
|
4
|
|
25
|
use Try::Tiny; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
1195
|
|
1001
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
sub new { |
1004
|
5
|
|
|
5
|
|
12
|
my $class = shift; |
1005
|
5
|
|
|
|
|
10
|
my @options = qw/-values/; |
1006
|
5
|
|
|
|
|
17
|
my $self = Data::Domain::_parse_args(\@_, \@options, -values => 'arrayref'); |
1007
|
5
|
|
|
|
|
12
|
bless $self, $class; |
1008
|
|
|
|
|
|
|
|
1009
|
5
|
50
|
|
5
|
|
28
|
try {@{$self->{-values}}} or croak "Enum : incorrect set of values"; |
|
5
|
|
|
|
|
124
|
|
|
5
|
|
|
|
|
47
|
|
1010
|
|
|
|
|
|
|
|
1011
|
5
|
100
|
|
|
|
66
|
not grep {! defined $_} @{$self->{-values}} |
|
19
|
|
|
|
|
162
|
|
|
5
|
|
|
|
|
12
|
|
1012
|
|
|
|
|
|
|
or croak "Enum : undefined element in values"; |
1013
|
|
|
|
|
|
|
|
1014
|
4
|
|
|
|
|
20
|
return $self; |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
sub _inspect { |
1019
|
6
|
|
|
6
|
|
14
|
my ($self, $data) = @_; |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
return $self->msg(NOT_IN_LIST => $data) |
1022
|
6
|
100
|
|
|
|
9
|
if not grep {$_ eq $data} @{$self->{-values}}; |
|
22
|
|
|
|
|
53
|
|
|
6
|
|
|
|
|
13
|
|
1023
|
|
|
|
|
|
|
|
1024
|
4
|
|
|
|
|
10
|
return; # otherwise OK, no error |
1025
|
|
|
|
|
|
|
} |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
#====================================================================== |
1029
|
|
|
|
|
|
|
package Data::Domain::List; |
1030
|
|
|
|
|
|
|
#====================================================================== |
1031
|
4
|
|
|
4
|
|
29
|
use strict; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
122
|
|
1032
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
138
|
|
1033
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
236
|
|
1034
|
4
|
|
|
4
|
|
26
|
use List::MoreUtils qw/all/; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
26
|
|
1035
|
4
|
|
|
4
|
|
4284
|
use Scalar::Does qw/does/; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
20
|
|
1036
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
sub new { |
1039
|
13
|
|
|
13
|
|
26
|
my $class = shift; |
1040
|
13
|
|
|
|
|
35
|
my @options = qw/-items -size -min_size -max_size -any -all/; |
1041
|
13
|
|
|
|
|
31
|
my $self = Data::Domain::_parse_args(\@_, \@options, -items => 'arrayref'); |
1042
|
13
|
|
|
|
|
28
|
bless $self, $class; |
1043
|
|
|
|
|
|
|
|
1044
|
13
|
|
|
|
|
39
|
$self->_expand_range(qw/-size -min_size -max_size/); |
1045
|
13
|
|
|
|
|
38
|
$self->_check_min_max(qw/-min_size -max_size <=/); |
1046
|
|
|
|
|
|
|
|
1047
|
12
|
100
|
|
|
|
31
|
if ($self->{-items}) { |
1048
|
5
|
50
|
|
|
|
19
|
does($self->{-items}, 'ARRAY') |
1049
|
|
|
|
|
|
|
or croak "invalid -items for Data::Domain::List"; |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
# if -items is given, then both -{min,max}_size cannot be shorter |
1052
|
5
|
|
|
|
|
109
|
for my $bound (qw/-min_size -max_size/) { |
1053
|
|
|
|
|
|
|
croak "$bound does not match -items" |
1054
|
10
|
50
|
33
|
|
|
27
|
if $self->{$bound} and $self->{$bound} < @{$self->{-items}}; |
|
0
|
|
|
|
|
0
|
|
1055
|
|
|
|
|
|
|
} |
1056
|
|
|
|
|
|
|
} |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
# check that -all or -any are domains or lists of domains |
1059
|
12
|
|
|
|
|
25
|
for my $arg (qw/-all -any/) { |
1060
|
24
|
100
|
|
|
|
162
|
if (my $dom = $self->{$arg}) { |
1061
|
8
|
100
|
|
|
|
448
|
$dom = [$dom] unless does($dom, 'ARRAY'); |
1062
|
8
|
100
|
|
9
|
|
529
|
all {does($_, 'Data::Domain') || does($_, 'CODE')} @$dom |
|
9
|
50
|
|
|
|
40
|
|
1063
|
|
|
|
|
|
|
or croak "invalid arg to $arg in Data::Domain::List"; |
1064
|
|
|
|
|
|
|
} |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
|
1067
|
12
|
|
|
|
|
138
|
return $self; |
1068
|
|
|
|
|
|
|
} |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
sub _inspect { |
1072
|
38
|
|
|
38
|
|
68
|
my ($self, $data, $context) = @_; |
1073
|
4
|
|
|
4
|
|
3115
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
2279
|
|
1074
|
|
|
|
|
|
|
|
1075
|
38
|
100
|
|
|
|
95
|
does($data, 'ARRAY') |
1076
|
|
|
|
|
|
|
or return $self->msg(NOT_A_LIST => $data); |
1077
|
|
|
|
|
|
|
|
1078
|
37
|
100
|
100
|
|
|
779
|
if (defined $self->{-min_size} && @$data < $self->{-min_size}) { |
1079
|
1
|
|
|
|
|
4
|
return $self->msg(TOO_SHORT => $self->{-min_size}); |
1080
|
|
|
|
|
|
|
} |
1081
|
|
|
|
|
|
|
|
1082
|
36
|
100
|
100
|
|
|
103
|
if (defined $self->{-max_size} && @$data > $self->{-max_size}) { |
1083
|
1
|
|
|
|
|
4
|
return $self->msg(TOO_LONG => $self->{-max_size}); |
1084
|
|
|
|
|
|
|
} |
1085
|
|
|
|
|
|
|
|
1086
|
35
|
100
|
100
|
|
|
125
|
return unless $self->{-items} || $self->{-all} || $self->{-any}; |
|
|
|
100
|
|
|
|
|
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
# prepare context for calling lazy subdomains |
1089
|
33
|
|
100
|
|
|
318
|
$context ||= {root => $data, |
1090
|
|
|
|
|
|
|
flat => {}, |
1091
|
|
|
|
|
|
|
path => []}; |
1092
|
33
|
|
|
|
|
80
|
local $context->{list} = $data; |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
# initializing some variables |
1095
|
33
|
|
|
|
|
50
|
my @msgs; |
1096
|
|
|
|
|
|
|
my $has_invalid; |
1097
|
33
|
|
100
|
|
|
84
|
my $items = $self->{-items} || []; |
1098
|
33
|
|
|
|
|
51
|
my $n_items = @$items; |
1099
|
33
|
|
|
|
|
43
|
my $n_data = @$data; |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
# check the -items conditions |
1102
|
33
|
|
|
|
|
81
|
for (my $i = 0; $i < $n_items; $i++) { |
1103
|
50
|
|
|
|
|
71
|
local $context->{path} = [@{$context->{path}}, $i]; |
|
50
|
|
|
|
|
127
|
|
1104
|
50
|
50
|
|
|
|
122
|
my $subdomain = $self->_build_subdomain($items->[$i], $context) |
1105
|
|
|
|
|
|
|
or next; |
1106
|
50
|
|
|
|
|
856
|
$msgs[$i] = $subdomain->inspect($data->[$i], $context); |
1107
|
50
|
|
100
|
|
|
246
|
$has_invalid ||= $msgs[$i]; |
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
# check the -all condition (can be a single domain or an arrayref of domains) |
1111
|
33
|
100
|
|
|
|
82
|
if (my $all = $self->{-all}) { |
1112
|
8
|
50
|
|
|
|
114
|
$all = [$all] unless does($all, 'ARRAY'); |
1113
|
8
|
|
|
|
|
448
|
my $n_all = @$all; |
1114
|
8
|
|
|
|
|
23
|
for (my $i = $n_items, my $j = 0; # $i iterates over @$data, $j over @$all |
1115
|
|
|
|
|
|
|
$i < $n_data; |
1116
|
|
|
|
|
|
|
$i++, $j = ($j + 1) % $n_all) { |
1117
|
28
|
|
|
|
|
45
|
local $context->{path} = [@{$context->{path}}, $i]; |
|
28
|
|
|
|
|
83
|
|
1118
|
28
|
|
|
|
|
64
|
my $subdomain = $self->_build_subdomain($all->[$j], $context); |
1119
|
28
|
|
|
|
|
69
|
$msgs[$i] = $subdomain->inspect($data->[$i], $context); |
1120
|
28
|
|
100
|
|
|
176
|
$has_invalid ||= $msgs[$i]; |
1121
|
|
|
|
|
|
|
} |
1122
|
|
|
|
|
|
|
} |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
# stop here if there was any error message |
1125
|
33
|
100
|
|
|
|
112
|
return \@msgs if $has_invalid; |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
# all other conditions were good, now check the "any" conditions |
1128
|
24
|
100
|
|
|
|
53
|
if (my $any = $self->{-any}) { |
1129
|
13
|
100
|
|
|
|
138
|
$any = [$any] unless does($any, 'ARRAY'); |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
# there must be data to inspect |
1132
|
|
|
|
|
|
|
$n_data > $n_items |
1133
|
13
|
100
|
33
|
|
|
752
|
or return $self->msg(ANY => ($any->[0]{-name} || $any->[0]->subclass)); |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
# inspect the remaining data for all 'any' conditions |
1136
|
|
|
|
|
|
|
CONDITION: |
1137
|
12
|
|
|
|
|
21
|
foreach my $condition (@$any) { |
1138
|
15
|
|
|
|
|
24
|
my $subdomain; |
1139
|
15
|
|
|
|
|
36
|
for (my $i = $n_items; $i < $n_data; $i++) { |
1140
|
31
|
|
|
|
|
41
|
local $context->{path} = [@{$context->{path}}, $i]; |
|
31
|
|
|
|
|
73
|
|
1141
|
31
|
|
|
|
|
66
|
$subdomain = $self->_build_subdomain($condition, $context); |
1142
|
31
|
|
|
|
|
70
|
my $error = $subdomain->inspect($data->[$i], $context); |
1143
|
31
|
100
|
|
|
|
109
|
next CONDITION if not $error; |
1144
|
|
|
|
|
|
|
} |
1145
|
4
|
|
33
|
|
|
13
|
return $self->msg(ANY => ($subdomain->{-name} || $subdomain->subclass)); |
1146
|
|
|
|
|
|
|
} |
1147
|
|
|
|
|
|
|
} |
1148
|
|
|
|
|
|
|
|
1149
|
19
|
|
|
|
|
118
|
return; # OK, no error |
1150
|
|
|
|
|
|
|
} |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
#====================================================================== |
1154
|
|
|
|
|
|
|
package Data::Domain::Struct; |
1155
|
|
|
|
|
|
|
#====================================================================== |
1156
|
4
|
|
|
4
|
|
48
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
102
|
|
1157
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
140
|
|
1158
|
4
|
|
|
4
|
|
38
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
248
|
|
1159
|
4
|
|
|
4
|
|
24
|
use Scalar::Does qw/does/; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
25
|
|
1160
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
sub new { |
1163
|
14
|
|
|
14
|
|
31
|
my $class = shift; |
1164
|
14
|
|
|
|
|
34
|
my @options = qw/-fields -exclude -keys -values/; |
1165
|
14
|
|
|
|
|
36
|
my $self = Data::Domain::_parse_args(\@_, \@options, -fields => 'arrayref'); |
1166
|
14
|
|
|
|
|
30
|
bless $self, $class; |
1167
|
|
|
|
|
|
|
|
1168
|
14
|
|
100
|
|
|
112
|
my $fields = $self->{-fields} || []; |
1169
|
14
|
100
|
|
|
|
44
|
if (does($fields, 'ARRAY')) { |
|
|
50
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# transform arrayref into hashref plus an ordered list of keys |
1171
|
13
|
|
|
|
|
826
|
$self->{-fields_list} = []; |
1172
|
13
|
|
|
|
|
29
|
$self->{-fields} = {}; |
1173
|
13
|
|
|
|
|
42
|
for (my $i = 0; $i < @$fields; $i += 2) { |
1174
|
22
|
|
|
|
|
56
|
my ($key, $val) = ($fields->[$i], $fields->[$i+1]); |
1175
|
22
|
|
|
|
|
27
|
push @{$self->{-fields_list}}, $key; |
|
22
|
|
|
|
|
46
|
|
1176
|
22
|
|
|
|
|
68
|
$self->{-fields}{$key} = $val; |
1177
|
|
|
|
|
|
|
} |
1178
|
|
|
|
|
|
|
} |
1179
|
|
|
|
|
|
|
elsif (does($fields, 'HASH')) { |
1180
|
|
|
|
|
|
|
# keep given hashref, add list of keys |
1181
|
1
|
|
|
|
|
46
|
$self->{-fields_list} = [keys %$fields]; |
1182
|
|
|
|
|
|
|
} |
1183
|
|
|
|
|
|
|
else { |
1184
|
0
|
|
|
|
|
0
|
croak "invalid data for -fields option"; |
1185
|
|
|
|
|
|
|
} |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
# check that -exclude is an arrayref or a regex or a string |
1188
|
14
|
100
|
|
|
|
43
|
if (my $exclude = $self->{-exclude}) { |
1189
|
3
|
50
|
100
|
|
|
7
|
does($exclude, 'ARRAY') || does($exclude, 'Regexp') || !ref($exclude) |
|
|
|
66
|
|
|
|
|
1190
|
|
|
|
|
|
|
or croak "invalid data for -exclude option"; |
1191
|
|
|
|
|
|
|
} |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
# check that -keys or -values are List domains |
1195
|
14
|
|
|
|
|
203
|
for my $arg (qw/-keys -values/) { |
1196
|
28
|
100
|
|
|
|
90
|
if (my $dom = $self->{$arg}) { |
1197
|
2
|
50
|
33
|
|
|
43
|
does($dom, 'Data::Domain::List') or does($dom, 'CODE') |
1198
|
|
|
|
|
|
|
or croak "$arg in Data::Domain::Struct should be a List domain"; |
1199
|
|
|
|
|
|
|
} |
1200
|
|
|
|
|
|
|
} |
1201
|
|
|
|
|
|
|
|
1202
|
14
|
|
|
|
|
94
|
return $self; |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
sub _inspect { |
1207
|
137
|
|
|
137
|
|
274
|
my ($self, $data, $context) = @_; |
1208
|
4
|
|
|
4
|
|
2958
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
1796
|
|
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
# check that $data is a hashref |
1211
|
137
|
100
|
|
|
|
352
|
does($data, 'HASH') |
1212
|
|
|
|
|
|
|
or return $self->msg(NOT_A_HASH => $data); |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
# check if there are any forbidden fields |
1215
|
135
|
100
|
|
|
|
3484
|
if (my $exclude = $self->{-exclude}) { |
1216
|
|
|
|
|
|
|
FIELD: |
1217
|
9
|
|
|
|
|
29
|
foreach my $field (keys %$data) { |
1218
|
15
|
100
|
|
|
|
107
|
next FIELD if $self->{-fields}{$field}; |
1219
|
|
|
|
|
|
|
|
1220
|
8
|
100
|
100
|
|
|
57
|
return $self->msg(FORBIDDEN_FIELD => $field) |
1221
|
|
|
|
|
|
|
if match::simple::match($field, $exclude) |
1222
|
|
|
|
|
|
|
or match::simple::match($exclude, ['*', 'all']); |
1223
|
|
|
|
|
|
|
} |
1224
|
|
|
|
|
|
|
} |
1225
|
|
|
|
|
|
|
|
1226
|
129
|
|
|
|
|
255
|
my %msgs; |
1227
|
|
|
|
|
|
|
my $has_invalid; |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
# prepare context for calling lazy subdomains |
1230
|
129
|
|
100
|
|
|
391
|
$context ||= {root => $data, |
1231
|
|
|
|
|
|
|
flat => {}, |
1232
|
|
|
|
|
|
|
list => [], |
1233
|
|
|
|
|
|
|
path => []}; |
1234
|
129
|
|
|
|
|
171
|
local $context->{flat} = {%{$context->{flat}}, %$data}; |
|
129
|
|
|
|
|
853
|
|
1235
|
|
|
|
|
|
|
|
1236
|
|
|
|
|
|
|
# check fields of the domain |
1237
|
129
|
|
|
|
|
249
|
foreach my $field (@{$self->{-fields_list}}) { |
|
129
|
|
|
|
|
253
|
|
1238
|
354
|
|
|
|
|
485
|
local $context->{path} = [@{$context->{path}}, $field]; |
|
354
|
|
|
|
|
3180
|
|
1239
|
354
|
|
|
|
|
714
|
my $field_spec = $self->{-fields}{$field}; |
1240
|
354
|
|
|
|
|
709
|
my $subdomain = $self->_build_subdomain($field_spec, $context); |
1241
|
353
|
|
|
|
|
1471
|
my $msg = $subdomain->inspect($data->{$field}, $context); |
1242
|
254
|
100
|
|
|
|
525
|
$msgs{$field} = $msg if $msg; |
1243
|
254
|
|
100
|
|
|
1377
|
$has_invalid ||= $msg; |
1244
|
|
|
|
|
|
|
} |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
# check the List domain for keys |
1247
|
29
|
100
|
|
|
|
73
|
if (my $keys_dom = $self->{-keys}) { |
1248
|
3
|
|
|
|
|
66
|
local $context->{path} = [@{$context->{path}}, "-keys"]; |
|
3
|
|
|
|
|
10
|
|
1249
|
3
|
|
|
|
|
10
|
my $subdomain = $self->_build_subdomain($keys_dom, $context); |
1250
|
3
|
100
|
|
|
|
11
|
$msgs{-keys} = $subdomain->inspect([keys %$data], $context) |
1251
|
|
|
|
|
|
|
and $has_invalid = 1; |
1252
|
|
|
|
|
|
|
} |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
# check the List domain for values |
1255
|
29
|
100
|
|
|
|
92
|
if (my $values_dom = $self->{-values}) { |
1256
|
3
|
|
|
|
|
52
|
local $context->{path} = [@{$context->{path}}, "-values"]; |
|
3
|
|
|
|
|
9
|
|
1257
|
3
|
|
|
|
|
8
|
my $subdomain = $self->_build_subdomain($values_dom, $context); |
1258
|
3
|
100
|
|
|
|
11
|
$msgs{-values} = $subdomain->inspect([values %$data], $context) |
1259
|
|
|
|
|
|
|
and $has_invalid = 1; |
1260
|
|
|
|
|
|
|
} |
1261
|
|
|
|
|
|
|
|
1262
|
29
|
100
|
|
|
|
226
|
return $has_invalid ? \%msgs : undef; |
1263
|
|
|
|
|
|
|
} |
1264
|
|
|
|
|
|
|
|
1265
|
|
|
|
|
|
|
#====================================================================== |
1266
|
|
|
|
|
|
|
package Data::Domain::One_of; |
1267
|
|
|
|
|
|
|
#====================================================================== |
1268
|
4
|
|
|
4
|
|
29
|
use strict; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
261
|
|
1269
|
4
|
|
|
4
|
|
25
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
138
|
|
1270
|
4
|
|
|
4
|
|
73
|
use Carp; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
795
|
|
1271
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
sub new { |
1274
|
2
|
|
|
2
|
|
4
|
my $class = shift; |
1275
|
2
|
|
|
|
|
6
|
my @options = qw/-options/; |
1276
|
2
|
|
|
|
|
8
|
my $self = Data::Domain::_parse_args(\@_, \@options, -options => 'arrayref'); |
1277
|
2
|
|
|
|
|
5
|
bless $self, $class; |
1278
|
|
|
|
|
|
|
|
1279
|
2
|
50
|
|
|
|
42
|
Scalar::Does::does($self->{-options}, 'ARRAY') |
1280
|
|
|
|
|
|
|
or croak "One_of: invalid options"; |
1281
|
|
|
|
|
|
|
|
1282
|
2
|
|
|
|
|
51
|
return $self; |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
sub _inspect { |
1287
|
213
|
|
|
213
|
|
366
|
my ($self, $data, $context) = @_; |
1288
|
213
|
|
|
|
|
293
|
my @msgs; |
1289
|
4
|
|
|
4
|
|
29
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
552
|
|
1290
|
|
|
|
|
|
|
|
1291
|
213
|
|
|
|
|
280
|
for my $subdomain (@{$self->{-options}}) { |
|
213
|
|
|
|
|
449
|
|
1292
|
321
|
100
|
|
|
|
1265
|
my $msg = $subdomain->inspect($data, $context) |
1293
|
|
|
|
|
|
|
or return; # $subdomain was successful |
1294
|
112
|
|
|
|
|
283
|
push @msgs, $msg; |
1295
|
|
|
|
|
|
|
} |
1296
|
4
|
|
|
|
|
21
|
return \@msgs; |
1297
|
|
|
|
|
|
|
} |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
|
1300
|
|
|
|
|
|
|
#====================================================================== |
1301
|
|
|
|
|
|
|
package Data::Domain::All_of; |
1302
|
|
|
|
|
|
|
#====================================================================== |
1303
|
4
|
|
|
4
|
|
34
|
use strict; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
132
|
|
1304
|
4
|
|
|
4
|
|
23
|
use warnings; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
131
|
|
1305
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
799
|
|
1306
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
sub new { |
1309
|
1
|
|
|
1
|
|
2
|
my $class = shift; |
1310
|
1
|
|
|
|
|
3
|
my @options = qw/-options/; |
1311
|
1
|
|
|
|
|
4
|
my $self = Data::Domain::_parse_args(\@_, \@options, -options => 'arrayref'); |
1312
|
1
|
|
|
|
|
4
|
bless $self, $class; |
1313
|
|
|
|
|
|
|
|
1314
|
1
|
50
|
|
|
|
36
|
Scalar::Does::does($self->{-options}, 'ARRAY') |
1315
|
|
|
|
|
|
|
or croak "All_of: invalid options"; |
1316
|
|
|
|
|
|
|
|
1317
|
1
|
|
|
|
|
31
|
return $self; |
1318
|
|
|
|
|
|
|
} |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
|
1321
|
|
|
|
|
|
|
sub _inspect { |
1322
|
3
|
|
|
3
|
|
6
|
my ($self, $data, $context) = @_; |
1323
|
3
|
|
|
|
|
7
|
my @msgs; |
1324
|
4
|
|
|
4
|
|
30
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
534
|
|
1325
|
|
|
|
|
|
|
|
1326
|
3
|
|
|
|
|
5
|
for my $subdomain (@{$self->{-options}}) { |
|
3
|
|
|
|
|
7
|
|
1327
|
6
|
|
|
|
|
14
|
my $msg = $subdomain->inspect($data, $context); |
1328
|
6
|
100
|
|
|
|
19
|
push @msgs, $msg if $msg; # subdomain failed |
1329
|
|
|
|
|
|
|
} |
1330
|
3
|
100
|
|
|
|
18
|
return @msgs ? \@msgs : undef; |
1331
|
|
|
|
|
|
|
} |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
#====================================================================== |
1335
|
|
|
|
|
|
|
1; |
1336
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
|
1338
|
|
|
|
|
|
|
__END__ |