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   1071 use strict;
  38         125  
  38         1661  
2 38     38   212 use warnings;
  38         81  
  38         3465  
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.634';
8              
9 38     38   667 use 5.020;
  38         184  
10 38     38   236 use Moo;
  38         107  
  38         265  
11 38     38   17463 use strictures 2;
  38         343  
  38         1667  
12 38     38   19449 use stable 0.031 'postderef';
  38         789  
  38         295  
13 38     38   7613 use experimental 'signatures';
  38         105  
  38         198  
14 38     38   2821 no autovivification warn => qw(fetch store exists delete);
  38         152  
  38         316  
15 38     38   3394 use if "$]" >= 5.022, experimental => 're_strict';
  38         171  
  38         971  
16 38     38   3996 no if "$]" >= 5.031009, feature => 'indirect';
  38         91  
  38         3111  
17 38     38   265 no if "$]" >= 5.033001, feature => 'multidimensional';
  38         111  
  38         2829  
18 38     38   302 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  38         129  
  38         2737  
19 38     38   281 no if "$]" >= 5.041009, feature => 'smartmatch';
  38         93  
  38         2366  
20 38     38   247 no feature 'switch';
  38         89  
  38         1581  
21 38     38   243 use List::Util 'max';
  38         127  
  38         4173  
22 38     38   287 use if "$]" < 5.041010, 'List::Util' => 'any';
  38         85  
  38         2220  
23 38     38   269 use if "$]" >= 5.041010, experimental => 'keyword_any';
  38         91  
  38         781  
24 38     38   3783 use JSON::Schema::Modern::Utilities qw(is_type jsonp local_annotations E A abort true);
  38         128  
  38         3721  
25 38     38   302 use namespace::clean;
  38         97  
  38         416  
26              
27             with 'JSON::Schema::Modern::Vocabulary';
28              
29 22     22 0 46 sub vocabulary ($class) {
  22         45  
  22         45  
30 22         100 '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 2211 sub keywords ($class, $spec_version) {
  62         155  
  62         181  
  62         156  
37 62 100       509 die 'Unevaluated not implemented in '.$spec_version if $spec_version =~ /^draft(?:[467]|2019-09)\z/;
38 58         1227 qw(unevaluatedItems unevaluatedProperties);
39             }
40              
41 587     587   1669 sub _traverse_keyword_unevaluatedItems ($class, $schema, $state) {
  587         1585  
  587         1474  
  587         1317  
  587         1316  
42 587         3727 $class->traverse_subschema($schema, $state);
43             }
44              
45 554     554   1537 sub _eval_keyword_unevaluatedItems ($class, $data, $schema, $state) {
  554         1737  
  554         1476  
  554         1280  
  554         1250  
  554         1407  
46 554 100       2528 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       2023 if not $state->{collect_annotations};
51              
52 420         2089 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       2749 $state->{specification_version} eq 'draft2019-09' ? qw(items additionalItems unevaluatedItems)
57             : qw(prefixItems items contains unevaluatedItems);
58 420         1405 my %bools; @bools{@boolean_annotation_keywords} = (1)x@boolean_annotation_keywords;
  420         2983  
59             return 1
60 420 100 66     1580 if any { $bools{$_->{keyword}} && is_type('boolean', $_->{annotation}) && $_->{annotation} }
  264 100       2129  
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       1547 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       1901 map +($_->{keyword} eq $max_index_annotation_keyword ? $_->{annotation} : ()), @annotations);
68              
69 317 100       1783 return 1 if $last_index == $data->$#*;
70              
71             my @contains_annotation_indexes = $state->{specification_version} eq 'draft2019-09' ? ()
72 223 100       1249 : map +($_->{keyword} eq 'contains' ? $_->{annotation}->@* : ()), @annotations;
    100          
73              
74 223         512 my $valid = 1;
75 223         1058 foreach my $idx ($last_index+1 .. $data->$#*) {
76 312 100       1085 next if any { $idx == $_ } @contains_annotation_indexes;
  183         462  
77 256 100       1154 if (is_type('boolean', $schema->{unevaluatedItems})) {
78 194 100       1224 next if $schema->{unevaluatedItems};
79 174         5034 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
80             'additional item not permitted')
81             }
82             else {
83 62 100       1581 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         439 next;
88             }
89              
90 18         160 $valid = 0;
91             }
92 192 100       1998 last if $state->{short_circuit};
93             }
94              
95 223         1234 A($state, true);
96 223 100       1082 return E($state, 'subschema is not valid against all additional items') if not $valid;
97 40         300 return 1;
98             }
99              
100 994     994   2662 sub _traverse_keyword_unevaluatedProperties ($class, $schema, $state) {
  994         2753  
  994         2395  
  994         2622  
  994         2249  
101 994         6023 $class->traverse_subschema($schema, $state);
102             }
103              
104 932     932   2556 sub _eval_keyword_unevaluatedProperties ($class, $data, $schema, $state) {
  932         2297  
  932         2036  
  932         2015  
  932         1928  
  932         2171  
105 932 100       3800 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       3905 if not $state->{collect_annotations};
110              
111             my @evaluated_properties = map {
112 819         3503 my $keyword = $_->{keyword};
  707         2090  
113             (grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties))
114 707 100       4693 ? $_->{annotation}->@* : ();
115             } local_annotations($state);
116              
117 819         1925 my $valid = 1;
118 819         2245 my @properties;
119 819         4537 foreach my $property (sort keys %$data) {
120 1053 100       3373 next if any { $_ eq $property } @evaluated_properties;
  939         3524  
121 485         1563 push @properties, $property;
122              
123 485 100       2033 if (is_type('boolean', $schema->{unevaluatedProperties})) {
124 450 100       2897 next if $schema->{unevaluatedProperties};
125 317         5728 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
126             'additional property not permitted');
127             }
128             else {
129 35 100       354 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         195 next;
134             }
135              
136 13         111 $valid = 0;
137             }
138 330 100       3348 last if $state->{short_circuit};
139             }
140              
141 819         4696 A($state, \@properties);
142 819 100       3432 return E($state, 'not all additional properties are valid') if not $valid;
143 511         2704 return 1;
144             }
145              
146             1;
147              
148             __END__