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   983 use strict;
  38         129  
  38         1601  
2 38     38   211 use warnings;
  38         85  
  38         3346  
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.632';
8              
9 38     38   777 use 5.020;
  38         154  
10 38     38   242 use Moo;
  38         101  
  38         312  
11 38     38   16548 use strictures 2;
  38         351  
  38         1682  
12 38     38   17812 use stable 0.031 'postderef';
  38         787  
  38         299  
13 38     38   8269 use experimental 'signatures';
  38         135  
  38         192  
14 38     38   2739 no autovivification warn => qw(fetch store exists delete);
  38         108  
  38         326  
15 38     38   3420 use if "$]" >= 5.022, experimental => 're_strict';
  38         102  
  38         862  
16 38     38   3686 no if "$]" >= 5.031009, feature => 'indirect';
  38         122  
  38         2970  
17 38     38   234 no if "$]" >= 5.033001, feature => 'multidimensional';
  38         77  
  38         2675  
18 38     38   232 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  38         92  
  38         2587  
19 38     38   221 no if "$]" >= 5.041009, feature => 'smartmatch';
  38         137  
  38         2056  
20 38     38   285 no feature 'switch';
  38         93  
  38         1336  
21 38     38   201 use List::Util 'max';
  38         96  
  38         3993  
22 38     38   258 use if "$]" < 5.041010, 'List::Util' => 'any';
  38         92  
  38         2049  
23 38     38   265 use if "$]" >= 5.041010, experimental => 'keyword_any';
  38         113  
  38         675  
24 38     38   4454 use JSON::Schema::Modern::Utilities qw(is_type jsonp local_annotations E A abort true);
  38         97  
  38         4345  
25 38     38   271 use namespace::clean;
  38         87  
  38         402  
26              
27             with 'JSON::Schema::Modern::Vocabulary';
28              
29 22     22 0 63 sub vocabulary ($class) {
  22         52  
  22         49  
30 22         113 '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 1196 sub keywords ($class, $spec_version) {
  62         191  
  62         143  
  62         124  
37 62 100       451 die 'Unevaluated not implemented in '.$spec_version if $spec_version =~ /^draft(?:[467]|2019-09)\z/;
38 58         989 qw(unevaluatedItems unevaluatedProperties);
39             }
40              
41 587     587   1781 sub _traverse_keyword_unevaluatedItems ($class, $schema, $state) {
  587         1865  
  587         1563  
  587         1514  
  587         1505  
42 587         4915 $class->traverse_subschema($schema, $state);
43             }
44              
45 554     554   1760 sub _eval_keyword_unevaluatedItems ($class, $data, $schema, $state) {
  554         1830  
  554         1541  
  554         1367  
  554         1558  
  554         1191  
46 554 100       2805 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       2514 if not $state->{collect_annotations};
51              
52 420         2159 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       3083 $state->{specification_version} eq 'draft2019-09' ? qw(items additionalItems unevaluatedItems)
57             : qw(prefixItems items contains unevaluatedItems);
58 420         975 my %bools; @bools{@boolean_annotation_keywords} = (1)x@boolean_annotation_keywords;
  420         3360  
59             return 1
60 420 100 66     1688 if any { $bools{$_->{keyword}} && is_type('boolean', $_->{annotation}) && $_->{annotation} }
  264 100       2302  
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       1745 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       2263 map +($_->{keyword} eq $max_index_annotation_keyword ? $_->{annotation} : ()), @annotations);
68              
69 317 100       1922 return 1 if $last_index == $data->$#*;
70              
71             my @contains_annotation_indexes = $state->{specification_version} eq 'draft2019-09' ? ()
72 223 100       1532 : map +($_->{keyword} eq 'contains' ? $_->{annotation}->@* : ()), @annotations;
    100          
73              
74 223         720 my $valid = 1;
75 223         1373 foreach my $idx ($last_index+1 .. $data->$#*) {
76 312 100       1116 next if any { $idx == $_ } @contains_annotation_indexes;
  183         403  
77 256 100       1437 if (is_type('boolean', $schema->{unevaluatedItems})) {
78 194 100       1331 next if $schema->{unevaluatedItems};
79 174         5289 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
80             'additional item not permitted')
81             }
82             else {
83 62 100       3311 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         508 next;
88             }
89              
90 18         263 $valid = 0;
91             }
92 192 100       2204 last if $state->{short_circuit};
93             }
94              
95 223         1446 A($state, true);
96 223 100       1097 return E($state, 'subschema is not valid against all additional items') if not $valid;
97 40         370 return 1;
98             }
99              
100 994     994   2265 sub _traverse_keyword_unevaluatedProperties ($class, $schema, $state) {
  994         2820  
  994         2339  
  994         2045  
  994         1964  
101 994         5998 $class->traverse_subschema($schema, $state);
102             }
103              
104 932     932   2503 sub _eval_keyword_unevaluatedProperties ($class, $data, $schema, $state) {
  932         2194  
  932         2125  
  932         2261  
  932         1885  
  932         1626  
105 932 100       3910 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       3953 if not $state->{collect_annotations};
110              
111             my @evaluated_properties = map {
112 819         3933 my $keyword = $_->{keyword};
  707         2054  
113             (grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties))
114 707 100       4690 ? $_->{annotation}->@* : ();
115             } local_annotations($state);
116              
117 819         1882 my $valid = 1;
118 819         1649 my @properties;
119 819         4796 foreach my $property (sort keys %$data) {
120 1053 100       3770 next if any { $_ eq $property } @evaluated_properties;
  939         3419  
121 485         1534 push @properties, $property;
122              
123 485 100       2109 if (is_type('boolean', $schema->{unevaluatedProperties})) {
124 450 100       2777 next if $schema->{unevaluatedProperties};
125 317         5739 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
126             'additional property not permitted');
127             }
128             else {
129 35 100       408 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         233 next;
134             }
135              
136 13         172 $valid = 0;
137             }
138 330 100       3437 last if $state->{short_circuit};
139             }
140              
141 819         4976 A($state, \@properties);
142 819 100       3143 return E($state, 'not all additional properties are valid') if not $valid;
143 511         2565 return 1;
144             }
145              
146             1;
147              
148             __END__