File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm
Criterion Covered Total %
statement 137 141 97.1
branch 50 52 96.1
condition 2 3 66.6
subroutine 25 26 96.1
pod 0 3 0.0
total 214 225 95.1


line stmt bran cond sub pod time code
1 38     38   644 use strict;
  38         66  
  38         1273  
2 38     38   164 use warnings;
  38         60  
  38         4110  
3             package JSON::Schema::Modern::Vocabulary::Unevaluated;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema Unevaluated vocabulary
6              
7             our $VERSION = '0.637';
8              
9 38     38   581 use 5.020;
  38         120  
10 38     38   171 use Moo;
  38         72  
  38         215  
11 38     38   11830 use strictures 2;
  38         286  
  38         1196  
12 38     38   12830 use stable 0.031 'postderef';
  38         528  
  38         210  
13 38     38   5355 use experimental 'signatures';
  38         84  
  38         157  
14 38     38   1870 no autovivification warn => qw(fetch store exists delete);
  38         68  
  38         175  
15 38     38   2401 use if "$]" >= 5.022, experimental => 're_strict';
  38         74  
  38         701  
16 38     38   2511 no if "$]" >= 5.031009, feature => 'indirect';
  38         74  
  38         2032  
17 38     38   165 no if "$]" >= 5.033001, feature => 'multidimensional';
  38         66  
  38         1755  
18 38     38   193 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  38         76  
  38         1851  
19 38     38   169 no if "$]" >= 5.041009, feature => 'smartmatch';
  38         73  
  38         1360  
20 38     38   156 no feature 'switch';
  38         93  
  38         976  
21 38     38   144 use List::Util 'max';
  38         61  
  38         2712  
22 38     38   198 use if "$]" < 5.041010, 'List::Util' => 'any';
  38         96  
  38         1385  
23 38     38   155 use if "$]" >= 5.041010, experimental => 'keyword_any';
  38         65  
  38         475  
24 38     38   2615 use JSON::Schema::Modern::Utilities qw(is_type jsonp local_annotations E A abort true);
  38         106  
  38         2378  
25 38     38   187 use namespace::clean;
  38         73  
  38         256  
26              
27             with 'JSON::Schema::Modern::Vocabulary';
28              
29 22     22 0 58 sub vocabulary ($class) {
  22         35  
  22         33  
30 22         60 'https://json-schema.org/draft/2020-12/vocab/unevaluated' => 'draft2020-12';
31             }
32              
33 0     0 0 0 sub evaluation_order ($class) { 7 }
  0         0  
  0         0  
  0         0  
34              
35             # This vocabulary should be evaluated after the Applicator vocabulary.
36 62     62 0 278 sub keywords ($class, $spec_version) {
  62         130  
  62         111  
  62         96  
37 62 100       297 die 'Unevaluated not implemented in '.$spec_version if $spec_version =~ /^draft(?:[467]|2019-09)\z/;
38 58         646 qw(unevaluatedItems unevaluatedProperties);
39             }
40              
41 587     587   1212 sub _traverse_keyword_unevaluatedItems ($class, $schema, $state) {
  587         1069  
  587         1122  
  587         1067  
  587         944  
42 587         2421 $class->traverse_subschema($schema, $state);
43             }
44              
45 554     554   1182 sub _eval_keyword_unevaluatedItems ($class, $data, $schema, $state) {
  554         995  
  554         1067  
  554         976  
  554         911  
  554         829  
46 554 100       1687 return 1 if not is_type('array', $data);
47              
48             # these should never happen
49             die '"unevaluatedItems" keyword present, but annotation collection is disabled'
50 420 50       1516 if not $state->{collect_annotations};
51              
52 420         1255 my @annotations = local_annotations($state);
53              
54             # a relevant keyword already produced a 'true' annotation at this location
55             my @boolean_annotation_keywords =
56 420 100       1987 $state->{specification_version} eq 'draft2019-09' ? qw(items additionalItems unevaluatedItems)
57             : qw(prefixItems items contains unevaluatedItems);
58 420         662 my %bools; @bools{@boolean_annotation_keywords} = (1)x@boolean_annotation_keywords;
  420         2178  
59             return 1
60 420 100 66     1148 if any { $bools{$_->{keyword}} && is_type('boolean', $_->{annotation}) && $_->{annotation} }
  264 100       1532  
61             @annotations;
62              
63             # otherwise, evaluate at every instance item greater than the max of all 'prefixItems'/numeric
64             # 'items' annotations that isn't in a 'contains' annotation
65 317 100       1080 my $max_index_annotation_keyword = $state->{specification_version} eq 'draft2019-09' ? 'items' : 'prefixItems';
66             my $last_index = max(-1, grep is_type('integer', $_),
67 317 100       1471 map +($_->{keyword} eq $max_index_annotation_keyword ? $_->{annotation} : ()), @annotations);
68              
69 317 100       1247 return 1 if $last_index == $data->$#*;
70              
71             my @contains_annotation_indexes = $state->{specification_version} eq 'draft2019-09' ? ()
72 223 100       971 : map +($_->{keyword} eq 'contains' ? $_->{annotation}->@* : ()), @annotations;
    100          
73              
74 223         437 my $valid = 1;
75 223         893 foreach my $idx ($last_index+1 .. $data->$#*) {
76 312 100       765 next if any { $idx == $_ } @contains_annotation_indexes;
  183         338  
77 256 100       721 if (is_type('boolean', $schema->{unevaluatedItems})) {
78 194 100       895 next if $schema->{unevaluatedItems};
79 174         3631 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
80             'additional item not permitted')
81             }
82             else {
83 62 100       1089 if ($class->eval($data->[$idx], $schema->{unevaluatedItems},
84             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
85             keyword_path => $state->{keyword_path}.'/unevaluatedItems',
86             collect_annotations => $state->{collect_annotations} & ~1 })) {
87 44         360 next;
88             }
89              
90 18         128 $valid = 0;
91             }
92 192 100       1604 last if $state->{short_circuit};
93             }
94              
95 223         859 A($state, true);
96 223 100       738 return E($state, 'subschema is not valid against all additional items') if not $valid;
97 40         208 return 1;
98             }
99              
100 994     994   1878 sub _traverse_keyword_unevaluatedProperties ($class, $schema, $state) {
  994         1823  
  994         1652  
  994         1812  
  994         1945  
101 994         4077 $class->traverse_subschema($schema, $state);
102             }
103              
104 932     932   1623 sub _eval_keyword_unevaluatedProperties ($class, $data, $schema, $state) {
  932         1597  
  932         1854  
  932         1409  
  932         1222  
  932         1342  
105 932 100       2570 return 1 if not is_type('object', $data);
106              
107             # these should never happen
108             die '"unevaluatedProperties" keyword present, but annotation collection is disabled'
109 819 50       2675 if not $state->{collect_annotations};
110              
111             my @evaluated_properties = map {
112 819         2637 my $keyword = $_->{keyword};
  707         1456  
113             (grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties))
114 707 100       3661 ? $_->{annotation}->@* : ();
115             } local_annotations($state);
116              
117 819         1476 my $valid = 1;
118 819         1177 my @properties;
119 819         3540 foreach my $property (sort keys %$data) {
120 1053 100       2363 next if any { $_ eq $property } @evaluated_properties;
  939         2843  
121 485         1124 push @properties, $property;
122              
123 485 100       1390 if (is_type('boolean', $schema->{unevaluatedProperties})) {
124 450 100       1918 next if $schema->{unevaluatedProperties};
125 317         3782 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
126             'additional property not permitted');
127             }
128             else {
129 35 100       236 if ($class->eval($data->{$property}, $schema->{unevaluatedProperties},
130             +{ %$state, data_path => jsonp($state->{data_path}, $property),
131             keyword_path => $state->{keyword_path}.'/unevaluatedProperties',
132             collect_annotations => $state->{collect_annotations} & ~1 })) {
133 22         160 next;
134             }
135              
136 13         92 $valid = 0;
137             }
138 330 100       2410 last if $state->{short_circuit};
139             }
140              
141 819         3245 A($state, \@properties);
142 819 100       2246 return E($state, 'not all additional properties are valid') if not $valid;
143 511         1634 return 1;
144             }
145              
146             1;
147              
148             __END__