File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Unevaluated.pm
Criterion Covered Total %
statement 121 122 99.1
branch 49 56 87.5
condition 2 3 66.6
subroutine 23 24 95.8
pod 0 3 0.0
total 195 208 93.7


line stmt bran cond sub pod time code
1 31     31   730 use strict;
  31         85  
  31         1068  
2 31     31   218 use warnings;
  31         91  
  31         1708  
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.571';
8              
9 31     31   654 use 5.020;
  31         128  
10 31     31   219 use Moo;
  31         109  
  31         231  
11 31     31   12163 use strictures 2;
  31         285  
  31         1230  
12 31     31   5814 use stable 0.031 'postderef';
  31         645  
  31         211  
13 31     31   4544 use experimental 'signatures';
  31         121  
  31         169  
14 31     31   2774 use if "$]" >= 5.022, experimental => 're_strict';
  31         112  
  31         355  
15 31     31   2883 no if "$]" >= 5.031009, feature => 'indirect';
  31         119  
  31         255  
16 31     31   1662 no if "$]" >= 5.033001, feature => 'multidimensional';
  31         128  
  31         261  
17 31     31   1621 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  31         96  
  31         354  
18 31     31   1580 use List::Util 1.45 qw(any max);
  31         586  
  31         2735  
19 31     31   297 use JSON::Schema::Modern::Utilities qw(is_type jsonp local_annotations E A abort true);
  31         92  
  31         2608  
20 31     31   252 use namespace::clean;
  31         150  
  31         245  
21              
22             with 'JSON::Schema::Modern::Vocabulary';
23              
24             sub vocabulary {
25 15     15 0 64 'https://json-schema.org/draft/2020-12/vocab/unevaluated' => 'draft2020-12';
26             }
27              
28 0     0 0 0 sub evaluation_order { 7 }
29              
30             # This vocabulary should be evaluated after the Applicator vocabulary.
31 46     46 0 111 sub keywords ($self, $spec_version) {
  46         124  
  46         96  
  46         109  
32 46 50       317 die 'Unevaluated not implemented in '.$spec_version if $spec_version =~ /^draft[467]$/;
33 46         657 qw(unevaluatedItems unevaluatedProperties);
34             }
35              
36 539     539   1052 sub _traverse_keyword_unevaluatedItems ($self, $schema, $state) {
  539         1062  
  539         886  
  539         957  
  539         868  
37 539         1940 $self->traverse_subschema($schema, $state);
38             }
39              
40 542     542   1072 sub _eval_keyword_unevaluatedItems ($self, $data, $schema, $state) {
  542         1012  
  542         997  
  542         907  
  542         854  
  542         878  
41             # these should never happen
42             die '"unevaluatedItems" keyword present, but annotation collection is disabled'
43 542 50       1451 if not $state->{collect_annotations};
44             die '"unevaluatedItems" keyword present, but short_circuit is enabled: results unreliable'
45 542 50       1385 if $state->{short_circuit};
46              
47 542 100       1597 return 1 if not is_type('array', $data);
48              
49 374         1334 my @annotations = local_annotations($state);
50              
51             # a relevant keyword already produced a 'true' annotation at this location
52             my @boolean_annotation_keywords =
53 374 100       1503 $state->{spec_version} eq 'draft2019-09' ? qw(items additionalItems unevaluatedItems)
54             : qw(prefixItems items contains unevaluatedItems);
55 374         661 my %bools; @bools{@boolean_annotation_keywords} = (1)x@boolean_annotation_keywords;
  374         1717  
56             return 1
57 213 100 66 213   1364 if any { $bools{$_->{keyword}} && is_type('boolean', $_->{annotation}) && $_->{annotation} }
58 374 100       2644 @annotations;
59              
60             # otherwise, evaluate at every instance item greater than the max of all 'prefixItems'/numeric
61             # 'items' annotations that isn't in a 'contains' annotation
62 290 100       1988 my $max_index_annotation_keyword = $state->{spec_version} eq 'draft2019-09' ? 'items' : 'prefixItems';
63             my $last_index = max(-1, grep is_type('integer', $_),
64 290 100       1141 map +($_->{keyword} eq $max_index_annotation_keyword ? $_->{annotation} : ()), @annotations);
65              
66 290 100       1181 return 1 if $last_index == $data->$#*;
67              
68             my @contains_annotation_indexes = $state->{spec_version} eq 'draft2019-09' ? ()
69 200 100       764 : map +($_->{keyword} eq 'contains' ? $_->{annotation}->@* : ()), @annotations;
    100          
70              
71 200         382 my $valid = 1;
72 200         1093 foreach my $idx ($last_index+1 .. $data->$#*) {
73 288 100   154   1456 next if any { $idx == $_ } @contains_annotation_indexes;
  154         321  
74 242 100       1100 if (is_type('boolean', $schema->{unevaluatedItems})) {
75 178 100       2604 next if $schema->{unevaluatedItems};
76 158         3738 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
77             'additional item not permitted')
78             }
79             else {
80 64 100       1523 if ($self->eval($data->[$idx], $schema->{unevaluatedItems},
81             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
82             schema_path => $state->{schema_path}.'/unevaluatedItems',
83             collect_annotations => $state->{collect_annotations} & ~1 })) {
84 46         316 next;
85             }
86              
87 18         130 $valid = 0;
88             }
89 176 50       1554 last if $state->{short_circuit};
90             }
91              
92 200         937 A($state, true);
93 200 100       1132 return E($state, 'subschema is not valid against all additional items') if not $valid;
94 40         236 return 1;
95             }
96              
97 909     909   1756 sub _traverse_keyword_unevaluatedProperties ($self, $schema, $state) {
  909         1717  
  909         1624  
  909         1498  
  909         1462  
98 909         3476 $self->traverse_subschema($schema, $state);
99             }
100              
101 969     969   1894 sub _eval_keyword_unevaluatedProperties ($self, $data, $schema, $state) {
  969         2009  
  969         1704  
  969         1581  
  969         1597  
  969         1824  
102             # these should never happen
103             die '"unevaluatedProperties" keyword present, but annotation collection is disabled'
104 969 50       2778 if not $state->{collect_annotations};
105             die '"unevaluatedProperties" keyword present, but short_circuit is enabled: results unreliable'
106 969 50       2703 if $state->{short_circuit};
107              
108 969 100       2903 return 1 if not is_type('object', $data);
109              
110             my @evaluated_properties = map {
111 826         3358 my $keyword = $_->{keyword};
  661         1447  
112             (grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties))
113 661 100       2927 ? $_->{annotation}->@* : ();
114             } local_annotations($state);
115              
116 826         1637 my $valid = 1;
117 826         1342 my @properties;
118 826         3106 foreach my $property (sort keys %$data) {
119 1109 100   904   7220 next if any { $_ eq $property } @evaluated_properties;
  904         3350  
120 558         2424 push @properties, $property;
121              
122 558 100       1825 if (is_type('boolean', $schema->{unevaluatedProperties})) {
123 523 100       7178 next if $schema->{unevaluatedProperties};
124 326         4645 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
125             'additional property not permitted');
126             }
127             else {
128 35 100       452 if ($self->eval($data->{$property}, $schema->{unevaluatedProperties},
129             +{ %$state, data_path => jsonp($state->{data_path}, $property),
130             schema_path => $state->{schema_path}.'/unevaluatedProperties',
131             collect_annotations => $state->{collect_annotations} & ~1 })) {
132 22         161 next;
133             }
134              
135 13         94 $valid = 0;
136             }
137 339 50       2834 last if $state->{short_circuit};
138             }
139              
140 826         4127 A($state, \@properties);
141 826 100       2777 return E($state, 'not all additional properties are valid') if not $valid;
142 535         2457 return 1;
143             }
144              
145             1;
146              
147             __END__
148              
149             =pod
150              
151             =encoding UTF-8
152              
153             =head1 NAME
154              
155             JSON::Schema::Modern::Vocabulary::Unevaluated - Implementation of the JSON Schema Unevaluated vocabulary
156              
157             =head1 VERSION
158              
159             version 0.571
160              
161             =head1 DESCRIPTION
162              
163             =for Pod::Coverage vocabulary evaluation_order keywords
164              
165             =for stopwords metaschema
166              
167             Implementation of the JSON Schema Draft 2020-12 "Unevaluated" vocabulary, indicated in metaschemas
168             with the URI C<https://json-schema.org/draft/2020-12/vocab/unevaluated> and formally specified in
169             L<https://json-schema.org/draft/2020-12/json-schema-core.html#section-11>.
170              
171             Support is also provided for the equivalent Draft 2019-09 keywords in the
172             JSON Schema Draft 2019-09 "Applicator" vocabulary, indicated in metaschemas
173             with the URI C<https://json-schema.org/draft/2019-09/vocab/applicator> and formally specified in
174             L<https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02#section-9>.
175              
176             =for stopwords OpenAPI
177              
178             =head1 SUPPORT
179              
180             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Modern/issues>.
181              
182             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
183              
184             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
185             server|https://open-api.slack.com>, which are also great resources for finding help.
186              
187             =head1 AUTHOR
188              
189             Karen Etheridge <ether@cpan.org>
190              
191             =head1 COPYRIGHT AND LICENCE
192              
193             This software is copyright (c) 2020 by Karen Etheridge.
194              
195             This is free software; you can redistribute it and/or modify it under
196             the same terms as the Perl 5 programming language system itself.
197              
198             =cut