File Coverage

blib/lib/YAML/PP/Schema.pm
Criterion Covered Total %
statement 235 251 93.6
branch 101 118 85.5
condition 31 43 72.0
subroutine 27 29 93.1
pod 0 16 0.0
total 394 457 86.2


line stmt bran cond sub pod time code
1 45     45   141928 use strict;
  45         56  
  45         1220  
2 45     45   174 use warnings;
  45         82  
  45         1964  
3             package YAML::PP::Schema;
4 45     45   205 use B;
  45         56  
  45         989  
5 45     45   17320 use Module::Load qw//;
  45         48088  
  45         1586  
6              
7             our $VERSION = 'v0.40.0'; # VERSION
8              
9 45     45   12083 use YAML::PP::Common qw/ YAML_PLAIN_SCALAR_STYLE /;
  45         102  
  45         2899  
10              
11 45     45   245 use Carp qw/ croak /;
  45         117  
  45         1870  
12 45     45   216 use Scalar::Util qw/ blessed /;
  45         71  
  45         97197  
13              
14             sub new {
15 761     761 0 1991 my ($class, %args) = @_;
16              
17 761         1253 my $yaml_version = delete $args{yaml_version};
18 761         1245 my $bool = delete $args{boolean};
19 761 50       1442 $bool = 'perl' unless defined $bool;
20 761 50       1392 if (keys %args) {
21 0         0 die "Unexpected arguments: " . join ', ', sort keys %args;
22             }
23 761         1452 my $true;
24             my $false;
25 761         0 my @bool_class;
26 761         2194 my @bools = split m/,/, $bool;
27 761         1144 for my $b (@bools) {
28 754 50 66     2162 if ($b eq '*') {
    100          
    50          
    50          
29 0         0 push @bool_class, ('boolean', 'JSON::PP::Boolean');
30 0         0 last;
31             }
32             elsif ($b eq 'JSON::PP') {
33 595         4310 require JSON::PP;
34 595   50     2181 $true ||= \&_bool_jsonpp_true;
35 595   50     1862 $false ||= \&_bool_jsonpp_false;
36 595         1343 push @bool_class, 'JSON::PP::Boolean';
37             }
38             elsif ($b eq 'boolean') {
39 0         0 require boolean;
40 0   0     0 $true ||= \&_bool_booleanpm_true;
41 0   0     0 $false ||= \&_bool_booleanpm_false;
42 0         0 push @bool_class, 'boolean';
43             }
44             elsif ($b eq 'perl' or $b eq 'perl_experimental') {
45 159         372 push @bool_class, 'perl';
46             }
47             else {
48 0         0 die "Invalid value for 'boolean': '$b'. Allowed: ('perl', 'boolean', 'JSON::PP')";
49             }
50             }
51             # Ensure booleans are resolved
52 761   100     1870 $true ||= \&_bool_perl_true;
53 761   100     1663 $false ||= \&_bool_perl_false;
54              
55 761         6010 my %representers = (
56             'undef' => undef,
57             flags => [],
58             equals => {},
59             regex => [],
60             class_equals => {},
61             class_matches => [],
62             class_isa => [],
63             scalarref => undef,
64             refref => undef,
65             coderef => undef,
66             glob => undef,
67             tied_equals => {},
68             bool => undef,
69             );
70 761         3801 my $self = bless {
71             yaml_version => $yaml_version,
72             resolvers => {},
73             representers => \%representers,
74             true => $true,
75             false => $false,
76             bool_class => \@bool_class,
77             }, $class;
78 761         2473 return $self;
79             }
80              
81 33327     33327 0 40885 sub resolvers { return $_[0]->{resolvers} }
82 24825     24825 0 30104 sub representers { return $_[0]->{representers} }
83              
84 2266     2266 0 4447 sub true { return $_[0]->{true} }
85 2266     2266 0 3993 sub false { return $_[0]->{false} }
86 1586 100   1586 0 1646 sub bool_class { return @{ $_[0]->{bool_class} } ? $_[0]->{bool_class} : undef }
  1586         4426  
87 779     779 0 1633 sub yaml_version { return $_[0]->{yaml_version} }
88              
89             my %LOADED_SCHEMA = (
90             JSON => 1,
91             );
92             my %DEFAULT_SCHEMA = (
93             '1.2' => 'Core',
94             '1.1' => 'YAML1_1',
95             );
96              
97             sub load_subschemas {
98 761     761 0 1644 my ($self, @schemas) = @_;
99 761         1476 my $yaml_version = $self->yaml_version;
100 761         1056 my $i = 0;
101 761         1718 while ($i < @schemas) {
102 1380         1849 my $item = $schemas[ $i ];
103 1380 100       2301 if ($item eq '+') {
104 448         797 $item = $DEFAULT_SCHEMA{ $yaml_version };
105             }
106 1380         1497 $i++;
107 1380 100       2242 if (blessed($item)) {
108 10         32 $item->register(
109             schema => $self,
110             );
111 10         37 next;
112             }
113 1370         1549 my @options;
114 1370   100     5821 while ($i < @schemas
      100        
115             and (
116             $schemas[ $i ] =~ m/^[^A-Za-z]/
117             or
118             $schemas[ $i ] =~ m/^[a-zA-Z0-9]+=/
119             )
120             ) {
121 14         22 push @options, $schemas[ $i ];
122 14         24 $i++;
123             }
124              
125 1370         1463 my $class;
126 1370 100       2572 if ($item =~ m/^\:(.*)/) {
127 1         3 $class = "$1";
128 1 50       4 unless ($class =~ m/\A[A-Za-z0-9_:]+\z/) {
129 0         0 die "Module name '$class' is invalid";
130             }
131 1         5 Module::Load::load $class;
132             }
133             else {
134 1369         2172 $class = "YAML::PP::Schema::$item";
135 1369 50       5162 unless ($class =~ m/\A[A-Za-z0-9_:]+\z/) {
136 0         0 die "Module name '$class' is invalid";
137             }
138 1369   66     5317 $LOADED_SCHEMA{ $item } ||= Module::Load::load $class;
139             }
140 1370         78343 $class->register(
141             schema => $self,
142             options => \@options,
143             );
144              
145             }
146             }
147              
148             sub add_resolver {
149 21568     21568 0 32794 my ($self, %args) = @_;
150 21568         22169 my $tag = $args{tag};
151 21568         21587 my $rule = $args{match};
152 21568         24841 my $resolvers = $self->resolvers;
153 21568         26785 my ($type, @rule) = @$rule;
154 21568         21032 my $implicit = $args{implicit};
155 21568 100       27041 $implicit = 1 unless defined $implicit;
156 21568         19439 my $resolver_list = [];
157 21568 50       26525 if ($tag) {
158 21568 100       24658 if (ref $tag eq 'Regexp') {
159 681   100     1890 my $res = $resolvers->{tags} ||= [];
160 681         1391 push @$res, [ $tag, {} ];
161 681         1349 push @$resolver_list, $res->[-1]->[1];
162             }
163             else {
164 20887   100     35534 my $res = $resolvers->{tag}->{ $tag } ||= {};
165 20887         24232 push @$resolver_list, $res;
166             }
167             }
168 21568 100       26329 if ($implicit) {
169 20821   100     28947 push @$resolver_list, $resolvers->{value} ||= {};
170             }
171 21568         22685 for my $res (@$resolver_list) {
172 42389 100       49577 if ($type eq 'equals') {
    100          
    50          
173 34140         36007 my ($match, $value) = @rule;
174 34140 50       44742 unless (exists $res->{equals}->{ $match }) {
175 34140         45259 $res->{equals}->{ $match } = $value;
176             }
177 34140         65831 next;
178             }
179             elsif ($type eq 'regex') {
180 5994         6452 my ($match, $value) = @rule;
181 5994         5268 push @{ $res->{regex} }, [ $match => $value ];
  5994         13554  
182             }
183             elsif ($type eq 'all') {
184 2255         2533 my ($value) = @rule;
185 2255         5174 $res->{all} = $value;
186             }
187             }
188             }
189              
190             sub add_sequence_resolver {
191 669     669 0 1356 my ($self, %args) = @_;
192 669         1652 return $self->add_collection_resolver(sequence => %args);
193             }
194              
195             sub add_mapping_resolver {
196 874     874 0 1615 my ($self, %args) = @_;
197 874         1609 return $self->add_collection_resolver(mapping => %args);
198             }
199              
200             sub add_collection_resolver {
201 1543     1543 0 2863 my ($self, $type, %args) = @_;
202 1543         1764 my $tag = $args{tag};
203 1543         1652 my $implicit = $args{implicit};
204 1543         1784 my $resolvers = $self->resolvers;
205              
206 1543 100 66     3698 if ($tag and ref $tag eq 'Regexp') {
    50          
207 1394   100     3445 my $res = $resolvers->{ $type }->{tags} ||= [];
208             push @$res, [ $tag, {
209             on_create => $args{on_create},
210             on_data => $args{on_data},
211 1394         4745 } ];
212             }
213             elsif ($tag) {
214             my $res = $resolvers->{ $type }->{tag}->{ $tag } ||= {
215             on_create => $args{on_create},
216             on_data => $args{on_data},
217 149   50     845 };
218             }
219             }
220              
221             sub add_representer {
222 20974     20974 0 30661 my ($self, %args) = @_;
223              
224 20974         23725 my $representers = $self->representers;
225 20974 100       27556 if (my $flags = $args{flags}) {
226 1512         1767 my $rep = $representers->{flags};
227 1512         1869 push @$rep, \%args;
228 1512         2493 return;
229             }
230 19462 100       24381 if (my $regex = $args{regex}) {
231 757         1077 my $rep = $representers->{regex};
232 757         1291 push @$rep, \%args;
233 757         1262 return;
234             }
235 18705 100       23058 if (my $regex = $args{class_matches}) {
236 26         33 my $rep = $representers->{class_matches};
237 26         47 push @$rep, [ $args{class_matches}, $args{code} ];
238 26         60 return;
239             }
240 18679 100 66     25380 if (my $bool = $args{bool} and $] >= 5.036000) {
241             $representers->{bool} = {
242             code => $args{code},
243 156         336 };
244 156         304 return;
245             }
246 18523 100       24108 if (my $class_equals = $args{class_equals}) {
247 595         788 my $rep = $representers->{class_equals};
248             $rep->{ $class_equals } = {
249             code => $args{code},
250 595         1300 };
251 595         1265 return;
252             }
253 17928 100       21652 if (my $class_isa = $args{class_isa}) {
254 2         3 my $rep = $representers->{class_isa};
255 2         3 push @$rep, [ $args{class_isa}, $args{code} ];
256 2         4 return;
257             }
258 17926 50       21616 if (my $tied_equals = $args{tied_equals}) {
259 0         0 my $rep = $representers->{tied_equals};
260             $rep->{ $tied_equals } = {
261             code => $args{code},
262 0         0 };
263 0         0 return;
264             }
265 17926 100       23884 if (defined(my $equals = $args{equals})) {
266 17074         16502 my $rep = $representers->{equals};
267             $rep->{ $equals } = {
268             code => $args{code},
269 17074         26701 };
270 17074         34511 return;
271             }
272 852 100       1696 if (defined(my $scalarref = $args{scalarref})) {
273             $representers->{scalarref} = {
274             code => $args{code},
275 24         51 };
276 24         136 return;
277             }
278 828 100       1452 if (defined(my $refref = $args{refref})) {
279             $representers->{refref} = {
280             code => $args{code},
281 24         44 };
282 24         68 return;
283             }
284 804 100       1477 if (defined(my $coderef = $args{coderef})) {
285             $representers->{coderef} = {
286             code => $args{code},
287 24         44 };
288 24         39 return;
289             }
290 780 100       1413 if (defined(my $glob = $args{glob})) {
291             $representers->{glob} = {
292             code => $args{code},
293 24         42 };
294 24         41 return;
295             }
296 756 50       1471 if (my $undef = $args{undefined}) {
297 756         1153 $representers->{undef} = $undef;
298 756         1501 return;
299             }
300             }
301              
302             sub load_scalar {
303 7661     7661 0 10726 my ($self, $constructor, $event) = @_;
304 7661         17773 my $tag = $event->{tag};
305 7661         9528 my $value = $event->{value};
306              
307 7661         12766 my $resolvers = $self->resolvers;
308 7661         8248 my $res;
309 7661 100       9745 if ($tag) {
310 697 100       1290 if ($tag eq '!') {
311 7         18 return $value;
312             }
313 690         1419 $res = $resolvers->{tag}->{ $tag };
314 690 100 66     1641 if (not $res and my $matches = $resolvers->{tags}) {
315 56         94 for my $match (@$matches) {
316 76         118 my ($re, $rule) = @$match;
317 76 100       468 if ($tag =~ $re) {
318 56         80 $res = $rule;
319 56         92 last;
320             }
321             }
322             }
323             #unless ($res) {
324             # croak "Unknown tag '$tag'. Use schema 'Catchall' to ignore unknown tags";
325             #}
326             }
327             else {
328 6964         8163 $res = $resolvers->{value};
329 6964 100       12177 if ($event->{style} ne YAML_PLAIN_SCALAR_STYLE) {
330 2622         5714 return $value;
331             }
332             }
333              
334 5032 100       9281 if (my $equals = $res->{equals}) {
335 4412 100       8180 if (exists $equals->{ $value }) {
336 426         596 my $res = $equals->{ $value };
337 426 100       747 if (ref $res eq 'CODE') {
338 82         155 return $res->($constructor, $event);
339             }
340 344         667 return $res;
341             }
342             }
343 4606 100       7339 if (my $regex = $res->{regex}) {
344 3973         6393 for my $item (@$regex) {
345 14085         17473 my ($re, $sub) = @$item;
346 14085         54244 my @matches = $value =~ $re;
347 14085 100       23346 if (@matches) {
348 560         1754 return $sub->($constructor, $event, \@matches);
349             }
350             }
351             }
352 4046 100       6925 if (my $catch_all = $res->{all}) {
353 4019 50       7521 if (ref $catch_all eq 'CODE') {
354 4019         9258 return $catch_all->($constructor, $event);
355             }
356 0         0 return $catch_all;
357             }
358 27         74 return $value;
359             }
360              
361             sub create_sequence {
362 1230     1230 0 1730 my ($self, $constructor, $event) = @_;
363 1230         1601 my $tag = $event->{tag};
364 1230         1471 my $data = [];
365 1230         1282 my $on_data;
366              
367 1230         1983 my $resolvers = $self->resolvers->{sequence};
368 1230 100       1938 if ($tag) {
369 35 100       101 if (my $equals = $resolvers->{tag}->{ $tag }) {
370 6         9 my $on_create = $equals->{on_create};
371 6         10 $on_data = $equals->{on_data};
372 6 50       22 $on_create and $data = $on_create->($constructor, $event);
373 6         39 return ($data, $on_data);
374             }
375 29 50       73 if (my $matches = $resolvers->{tags}) {
376 29         47 for my $match (@$matches) {
377 31         58 my ($re, $actions) = @$match;
378 31         43 my $on_create = $actions->{on_create};
379 31 100       240 if ($tag =~ $re) {
380 29         49 $on_data = $actions->{on_data};
381 29 100       76 $on_create and $data = $on_create->($constructor, $event);
382 29         116 return ($data, $on_data);
383             }
384             }
385             }
386             #croak "Unknown tag '$tag'. Use schema 'Catchall' to ignore unknown tags";
387             }
388              
389 1195         2440 return ($data, $on_data);
390             }
391              
392             sub create_mapping {
393 1325     1325 0 1965 my ($self, $constructor, $event) = @_;
394 1325         1874 my $tag = $event->{tag};
395 1325         1654 my $data = {};
396 1325         1489 my $on_data;
397              
398 1325         2323 my $resolvers = $self->resolvers->{mapping};
399 1325 100       2062 if ($tag) {
400 83 100       270 if (my $equals = $resolvers->{tag}->{ $tag }) {
401 24         47 my $on_create = $equals->{on_create};
402 24         34 $on_data = $equals->{on_data};
403 24 100       116 $on_create and $data = $on_create->($constructor, $event);
404 24         96 return ($data, $on_data);
405             }
406 59 50       134 if (my $matches = $resolvers->{tags}) {
407 59         105 for my $match (@$matches) {
408 146         219 my ($re, $actions) = @$match;
409 146         181 my $on_create = $actions->{on_create};
410 146 100       627 if ($tag =~ $re) {
411 59         94 $on_data = $actions->{on_data};
412 59 100       183 $on_create and $data = $on_create->($constructor, $event);
413 59         205 return ($data, $on_data);
414             }
415             }
416             }
417             #croak "Unknown tag '$tag'. Use schema 'Catchall' to ignore unknown tags";
418             }
419              
420 1242         2660 return ($data, $on_data);
421             }
422              
423 31     31   109 sub _bool_jsonpp_true { JSON::PP::true() }
424              
425 0     0   0 sub _bool_booleanpm_true { boolean::true() }
426              
427 16     16   51 sub _bool_perl_true { !!1 }
428              
429 33     33   110 sub _bool_jsonpp_false { JSON::PP::false() }
430              
431 0     0   0 sub _bool_booleanpm_false { boolean::false() }
432              
433 2     2   4 sub _bool_perl_false { !!0 }
434              
435             1;
436              
437             __END__