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