File Coverage

blib/lib/YAML/PP/Schema.pm
Criterion Covered Total %
statement 233 249 93.5
branch 105 122 86.0
condition 30 43 69.7
subroutine 25 27 92.5
pod 0 16 0.0
total 393 457 86.0


line stmt bran cond sub pod time code
1 50     50   244036 use strict;
  50         73  
  50         1450  
2 50     50   190 use warnings;
  50         97  
  50         2999  
3             package YAML::PP::Schema;
4              
5             our $VERSION = 'v0.41.1'; # TRIAL VERSION
6              
7 50     50   18201 use YAML::PP::Common qw/ YAML_PLAIN_SCALAR_STYLE /;
  50         116  
  50         3404  
8              
9 50     50   293 use Carp qw/ croak /;
  50         116  
  50         2159  
10 50     50   222 use Scalar::Util qw/ blessed /;
  50         79  
  50         126003  
11              
12             sub new {
13 770     770 0 2636 my ($class, %args) = @_;
14              
15 770         1733 my $yaml_version = delete $args{yaml_version};
16 770         1651 my $bool = delete $args{boolean};
17 770 50       1962 $bool = 'perl' unless defined $bool;
18 770 50       2050 if (keys %args) {
19 0         0 die "Unexpected arguments: " . join ', ', sort keys %args;
20             }
21 770         1886 my $true;
22             my $false;
23 770         0 my @bool_class;
24 770         2413 my @bools = split m/,/, $bool;
25 770         1597 for my $b (@bools) {
26 763 50 66     2785 if ($b eq '*') {
    100          
    50          
    50          
27 0         0 push @bool_class, ('boolean', 'JSON::PP::Boolean');
28 0         0 last;
29             }
30             elsif ($b eq 'JSON::PP') {
31 595         4411 require JSON::PP;
32 595   50     3327 $true ||= \&_bool_jsonpp_true;
33 595   50     2471 $false ||= \&_bool_jsonpp_false;
34 595         1670 push @bool_class, 'JSON::PP::Boolean';
35             }
36             elsif ($b eq 'boolean') {
37 0         0 require boolean;
38 0   0     0 $true ||= \&_bool_booleanpm_true;
39 0   0     0 $false ||= \&_bool_booleanpm_false;
40 0         0 push @bool_class, 'boolean';
41             }
42             elsif ($b eq 'perl' or $b eq 'perl_experimental') {
43 168         405 push @bool_class, 'perl';
44             }
45             else {
46 0         0 die "Invalid value for 'boolean': '$b'. Allowed: ('perl', 'boolean', 'JSON::PP')";
47             }
48             }
49             # Ensure booleans are resolved
50 770   100     2319 $true ||= \&_bool_perl_true;
51 770   100     1949 $false ||= \&_bool_perl_false;
52              
53 770         7618 my %representers = (
54             'undef' => undef,
55             flags => [],
56             equals => {},
57             regex => [],
58             class_equals => {},
59             class_matches => [],
60             class_isa => [],
61             scalarref => undef,
62             refref => undef,
63             coderef => undef,
64             glob => undef,
65             tied_equals => {},
66             bool => undef,
67             );
68 770         4597 my $self = bless {
69             yaml_version => $yaml_version,
70             resolvers => {},
71             representers => \%representers,
72             true => $true,
73             false => $false,
74             bool_class => \@bool_class,
75             }, $class;
76 770         3218 return $self;
77             }
78              
79 52812     52812 0 65867 sub resolvers { return $_[0]->{resolvers} }
80 25077     25077 0 30317 sub representers { return $_[0]->{representers} }
81              
82 2293     2293 0 4623 sub true { return $_[0]->{true} }
83 2293     2293 0 4467 sub false { return $_[0]->{false} }
84 1604 100   1604 0 1680 sub bool_class { return @{ $_[0]->{bool_class} } ? $_[0]->{bool_class} : undef }
  1604         4508  
85 788     788 0 2013 sub yaml_version { return $_[0]->{yaml_version} }
86              
87             my %LOADED_SCHEMA = (
88             JSON => 1,
89             );
90             my %DEFAULT_SCHEMA = (
91             '1.2' => sub { require YAML::PP::Schema::Core; return 'YAML::PP::Schema::Core' },
92             '1.1' => sub { require YAML::PP::Schema::YAML1_1; return 'YAML::PP::Schema::YAML1_1' },
93             );
94             my %AVAILABLE_SCHEMAS = (
95             'Core' => sub { require YAML::PP::Schema::Core; return 'YAML::PP::Schema::Core' },
96             'JSON' => sub { require YAML::PP::Schema::JSON; return 'YAML::PP::Schema::JSON' },
97             'Failsafe' => sub { require YAML::PP::Schema::Failsafe; return 'YAML::PP::Schema::Failsafe' },
98             'Merge' => sub { require YAML::PP::Schema::Merge; return 'YAML::PP::Schema::Merge' },
99             'Perl' => sub { require YAML::PP::Schema::Perl; return 'YAML::PP::Schema::Perl' },
100             );
101              
102             sub load_subschemas {
103 770     770 0 2075 my ($self, @schemas) = @_;
104 770         2074 my $yaml_version = $self->yaml_version;
105 770         1242 my $i = 0;
106 770         2118 while ($i < @schemas) {
107 1389         2248 my $item = $schemas[ $i ];
108 1389         1744 $i++;
109 1389         1635 my $class;
110 1389 100       4474 if ($item eq '+') {
    100          
    100          
111 457         911 my $code = $DEFAULT_SCHEMA{ $yaml_version };
112 457         993 $class = $code->();
113             }
114             elsif ($AVAILABLE_SCHEMAS{ $item }) {
115 330         631 my $code = $AVAILABLE_SCHEMAS{ $item };
116 330         787 $class = $code->();
117             }
118             elsif (blessed($item)) {
119 10         37 $item->register(
120             schema => $self,
121             );
122 10         41 next;
123             }
124 1379         1848 my @options;
125 1379   100     7700 while ($i < @schemas
      100        
126             and (
127             $schemas[ $i ] =~ m/^[^A-Za-z]/
128             or
129             $schemas[ $i ] =~ m/^[a-zA-Z0-9]+=/
130             )
131             ) {
132 14         23 push @options, $schemas[ $i ];
133 14         23 $i++;
134             }
135              
136 1379 100       3234 unless ($class) {
137 592         5664 require Module::Load;
138 592 100       7946 if ($item =~ m/^\:(.*)/) {
139 1         3 $class = "$1";
140 1 50       5 unless ($class =~ m/\A[A-Za-z0-9_:]+\z/) {
141 0         0 die "Module name '$class' is invalid";
142             }
143 1         7 Module::Load::load $class;
144             }
145             else {
146 591         1054 $class = "YAML::PP::Schema::$item";
147 591 50       2753 unless ($class =~ m/\A[A-Za-z0-9_:]+\z/) {
148 0         0 die "Module name '$class' is invalid";
149             }
150 591   33     3534 $LOADED_SCHEMA{ $item } ||= Module::Load::load $class;
151             }
152             }
153             $class->register(
154 1379         44477 schema => $self,
155             options => \@options,
156             );
157              
158             }
159             }
160              
161             sub add_resolver {
162 21820     21820 0 34582 my ($self, %args) = @_;
163 21820         23750 my $tag = $args{tag};
164 21820         21348 my $rule = $args{match};
165 21820         26826 my $resolvers = $self->resolvers;
166 21820         29843 my ($type, @rule) = @$rule;
167 21820         21760 my $implicit = $args{implicit};
168 21820 100       29828 $implicit = 1 unless defined $implicit;
169 21820         19768 my $resolver_list = [];
170 21820 50       27369 if ($tag) {
171 21820 100       26383 if (ref $tag eq 'Regexp') {
172 681   100     2118 my $res = $resolvers->{tags} ||= [];
173 681         1457 push @$res, [ $tag, {} ];
174 681         1583 push @$resolver_list, $res->[-1]->[1];
175             }
176             else {
177 21139   100     37322 my $res = $resolvers->{tag}->{ $tag } ||= {};
178 21139         26584 push @$resolver_list, $res;
179             }
180             }
181 21820 100       27166 if ($implicit) {
182 21073   100     30861 push @$resolver_list, $resolvers->{value} ||= {};
183             }
184 21820         23468 for my $res (@$resolver_list) {
185 42893 100       51279 if ($type eq 'equals') {
    100          
    50          
186 34554         38073 my ($match, $value) = @rule;
187 34554 50       46031 unless (exists $res->{equals}->{ $match }) {
188 34554         48527 $res->{equals}->{ $match } = $value;
189             }
190 34554         69381 next;
191             }
192             elsif ($type eq 'regex') {
193 6066         6577 my ($match, $value) = @rule;
194 6066         5273 push @{ $res->{regex} }, [ $match => $value ];
  6066         14001  
195             }
196             elsif ($type eq 'all') {
197 2273         2471 my ($value) = @rule;
198 2273         5598 $res->{all} = $value;
199             }
200             }
201             }
202              
203             sub add_sequence_resolver {
204 669     669 0 4548 my ($self, %args) = @_;
205 669         1964 return $self->add_collection_resolver(sequence => %args);
206             }
207              
208             sub add_mapping_resolver {
209 874     874 0 1699 my ($self, %args) = @_;
210 874         1828 return $self->add_collection_resolver(mapping => %args);
211             }
212              
213             sub add_collection_resolver {
214 1543     1543 0 2842 my ($self, $type, %args) = @_;
215 1543         1788 my $tag = $args{tag};
216 1543         1740 my $implicit = $args{implicit};
217 1543         2142 my $resolvers = $self->resolvers;
218              
219 1543 100 66     4153 if ($tag and ref $tag eq 'Regexp') {
    50          
220 1394   100     4021 my $res = $resolvers->{ $type }->{tags} ||= [];
221             push @$res, [ $tag, {
222             on_create => $args{on_create},
223             on_data => $args{on_data},
224 1394         4983 } ];
225             }
226             elsif ($tag) {
227             my $res = $resolvers->{ $type }->{tag}->{ $tag } ||= {
228             on_create => $args{on_create},
229             on_data => $args{on_data},
230 149   50     818 };
231             }
232             }
233              
234             sub add_representer {
235 21226     21226 0 31704 my ($self, %args) = @_;
236              
237 21226         24766 my $representers = $self->representers;
238 21226 100       28503 if (my $flags = $args{flags}) {
239 1530         1843 my $rep = $representers->{flags};
240 1530         2016 push @$rep, \%args;
241 1530         2358 return;
242             }
243 19696 100       24411 if (my $regex = $args{regex}) {
244 766         921 my $rep = $representers->{regex};
245 766         1272 push @$rep, \%args;
246 766         1290 return;
247             }
248 18930 100       23208 if (my $regex = $args{class_matches}) {
249 26         31 my $rep = $representers->{class_matches};
250 26         51 push @$rep, [ $args{class_matches}, $args{code} ];
251 26         44 return;
252             }
253 18904 100 66     26444 if (my $bool = $args{bool} and $] >= 5.036000) {
254             $representers->{bool} = {
255             code => $args{code},
256 165         346 };
257 165         309 return;
258             }
259 18739 100       23349 if (my $class_equals = $args{class_equals}) {
260 595         933 my $rep = $representers->{class_equals};
261             $rep->{ $class_equals } = {
262             code => $args{code},
263 595         1308 };
264 595         1465 return;
265             }
266 18144 100       22402 if (my $class_isa = $args{class_isa}) {
267 2         2 my $rep = $representers->{class_isa};
268 2         3 push @$rep, [ $args{class_isa}, $args{code} ];
269 2         3 return;
270             }
271 18142 50       21650 if (my $tied_equals = $args{tied_equals}) {
272 0         0 my $rep = $representers->{tied_equals};
273             $rep->{ $tied_equals } = {
274             code => $args{code},
275 0         0 };
276 0         0 return;
277             }
278 18142 100       23509 if (defined(my $equals = $args{equals})) {
279 17281         17115 my $rep = $representers->{equals};
280             $rep->{ $equals } = {
281             code => $args{code},
282 17281         27944 };
283 17281         34993 return;
284             }
285 861 100       1774 if (defined(my $scalarref = $args{scalarref})) {
286             $representers->{scalarref} = {
287             code => $args{code},
288 24         99 };
289 24         49 return;
290             }
291 837 100       1511 if (defined(my $refref = $args{refref})) {
292             $representers->{refref} = {
293             code => $args{code},
294 24         42 };
295 24         41 return;
296             }
297 813 100       1530 if (defined(my $coderef = $args{coderef})) {
298             $representers->{coderef} = {
299             code => $args{code},
300 24         55 };
301 24         38 return;
302             }
303 789 100       1439 if (defined(my $glob = $args{glob})) {
304             $representers->{glob} = {
305             code => $args{code},
306 24         134 };
307 24         42 return;
308             }
309 765 50       1707 if (my $undef = $args{undefined}) {
310 765         1184 $representers->{undef} = $undef;
311 765         1537 return;
312             }
313             }
314              
315             sub load_scalar {
316 26588     26588 0 34045 my ($self, $constructor, $event) = @_;
317 26588         32158 my $tag = $event->{tag};
318 26588         31858 my $value = $event->{value};
319              
320 26588         40403 my $resolvers = $self->resolvers;
321 26588         26541 my $res;
322 26588 100       33431 if ($tag) {
323 697 100       1371 if ($tag eq '!') {
324 7         19 return $value;
325             }
326 690         1379 $res = $resolvers->{tag}->{ $tag };
327 690 100 66     1498 if (not $res and my $matches = $resolvers->{tags}) {
328 56         136 for my $match (@$matches) {
329 76         124 my ($re, $rule) = @$match;
330 76 100       478 if ($tag =~ $re) {
331 56         82 $res = $rule;
332 56         197 last;
333             }
334             }
335             }
336             #unless ($res) {
337             # croak "Unknown tag '$tag'. Use schema 'Catchall' to ignore unknown tags";
338             #}
339             }
340             else {
341 25891         28871 $res = $resolvers->{value};
342 25891 100       42431 if ($event->{style} ne YAML_PLAIN_SCALAR_STYLE) {
343 2658         5842 return $value;
344             }
345             }
346              
347 23923 100       37388 if (my $equals = $res->{equals}) {
348 23303 100       41680 if (exists $equals->{ $value }) {
349 426         563 my $res = $equals->{ $value };
350 426 100       854 if (ref $res eq 'CODE') {
351 82         159 return $res->($constructor, $event);
352             }
353 344         705 return $res;
354             }
355             }
356 23497 100       34197 if (my $regex = $res->{regex}) {
357 22864         31493 for my $item (@$regex) {
358 61704         74455 my ($re, $sub) = @$item;
359 61704         258465 my @matches = $value =~ $re;
360 61704 100       95349 if (@matches) {
361 9875         21220 return $sub->($constructor, $event, \@matches);
362             }
363             }
364             }
365 13622 100       21710 if (my $catch_all = $res->{all}) {
366 13595 50       25380 if (ref $catch_all eq 'CODE') {
367 13595         27324 return $catch_all->($constructor, $event);
368             }
369 0         0 return $catch_all;
370             }
371 27         64 return $value;
372             }
373              
374             sub create_sequence {
375 1230     1230 0 1832 my ($self, $constructor, $event) = @_;
376 1230         1679 my $tag = $event->{tag};
377 1230         1356 my $data = [];
378 1230         1266 my $on_data;
379              
380 1230         2065 my $resolvers = $self->resolvers->{sequence};
381 1230 100       2034 if ($tag) {
382 35 100       97 if (my $equals = $resolvers->{tag}->{ $tag }) {
383 6         10 my $on_create = $equals->{on_create};
384 6         9 $on_data = $equals->{on_data};
385 6 50       45 $on_create and $data = $on_create->($constructor, $event);
386 6         19 return ($data, $on_data);
387             }
388 29 50       71 if (my $matches = $resolvers->{tags}) {
389 29         62 for my $match (@$matches) {
390 31         56 my ($re, $actions) = @$match;
391 31         43 my $on_create = $actions->{on_create};
392 31 100       202 if ($tag =~ $re) {
393 29         45 $on_data = $actions->{on_data};
394 29 100       80 $on_create and $data = $on_create->($constructor, $event);
395 29         85 return ($data, $on_data);
396             }
397             }
398             }
399             #croak "Unknown tag '$tag'. Use schema 'Catchall' to ignore unknown tags";
400             }
401              
402 1195         2338 return ($data, $on_data);
403             }
404              
405             sub create_mapping {
406 1631     1631 0 2632 my ($self, $constructor, $event) = @_;
407 1631         2335 my $tag = $event->{tag};
408 1631         2027 my $data = {};
409 1631         1640 my $on_data;
410              
411 1631         2895 my $resolvers = $self->resolvers->{mapping};
412 1631 100       2683 if ($tag) {
413 83 100       248 if (my $equals = $resolvers->{tag}->{ $tag }) {
414 24         52 my $on_create = $equals->{on_create};
415 24         32 $on_data = $equals->{on_data};
416 24 100       94 $on_create and $data = $on_create->($constructor, $event);
417 24         74 return ($data, $on_data);
418             }
419 59 50       184 if (my $matches = $resolvers->{tags}) {
420 59         101 for my $match (@$matches) {
421 146         233 my ($re, $actions) = @$match;
422 146         206 my $on_create = $actions->{on_create};
423 146 100       666 if ($tag =~ $re) {
424 59         95 $on_data = $actions->{on_data};
425 59 100       191 $on_create and $data = $on_create->($constructor, $event);
426 59         191 return ($data, $on_data);
427             }
428             }
429             }
430             #croak "Unknown tag '$tag'. Use schema 'Catchall' to ignore unknown tags";
431             }
432              
433 1548         3583 return ($data, $on_data);
434             }
435              
436 31     31   97 sub _bool_jsonpp_true { JSON::PP::true() }
437              
438 0     0   0 sub _bool_booleanpm_true { boolean::true() }
439              
440 16     16   39 sub _bool_perl_true { !!1 }
441              
442 33     33   105 sub _bool_jsonpp_false { JSON::PP::false() }
443              
444 0     0   0 sub _bool_booleanpm_false { boolean::false() }
445              
446 2     2   4 sub _bool_perl_false { !!0 }
447              
448             1;
449              
450             __END__