File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Applicator.pm
Criterion Covered Total %
statement 366 366 100.0
branch 187 194 96.3
condition 41 44 93.1
subroutine 56 56 100.0
pod 0 3 0.0
total 650 663 98.0


line stmt bran cond sub pod time code
1 31     31   17689 use strict;
  31         94  
  31         1056  
2 31     31   196 use warnings;
  31         71  
  31         1675  
3             package JSON::Schema::Modern::Vocabulary::Applicator;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema Applicator vocabulary
6              
7             our $VERSION = '0.572';
8              
9 31     31   563 use 5.020;
  31         120  
10 31     31   191 use Moo;
  31         79  
  31         192  
11 31     31   11606 use strictures 2;
  31         230  
  31         1215  
12 31     31   5722 use stable 0.031 'postderef';
  31         508  
  31         191  
13 31     31   4633 use experimental 0.026 qw(signatures args_array_with_signatures);
  31         538  
  31         177  
14 31     31   3282 use if "$]" >= 5.022, experimental => 're_strict';
  31         107  
  31         271  
15 31     31   3147 no if "$]" >= 5.031009, feature => 'indirect';
  31         68  
  31         1077  
16 31     31   1743 no if "$]" >= 5.033001, feature => 'multidimensional';
  31         110  
  31         198  
17 31     31   1513 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  31         69  
  31         195  
18 31     31   1550 use List::Util 1.45 qw(any uniqstr);
  31         747  
  31         2710  
19 31     31   237 use Ref::Util 0.100 'is_plain_arrayref';
  31         499  
  31         1570  
20 31     31   15206 use Sub::Install;
  31         58075  
  31         147  
21 31     31   1304 use JSON::Schema::Modern::Utilities qw(is_type jsonp E A assert_keyword_type assert_pattern true is_elements_unique);
  31         82  
  31         2549  
22 31     31   14731 use JSON::Schema::Modern::Vocabulary::Unevaluated;
  31         116  
  31         1130  
23 31     31   227 use namespace::clean;
  31         69  
  31         142  
24              
25             with 'JSON::Schema::Modern::Vocabulary';
26              
27             sub vocabulary {
28 160     160 0 841 'https://json-schema.org/draft/2019-09/vocab/applicator' => 'draft2019-09',
29             'https://json-schema.org/draft/2020-12/vocab/applicator' => 'draft2020-12';
30             }
31              
32 9     9 0 48 sub evaluation_order { 3 }
33              
34             # the keyword order is arbitrary, except:
35             # - if must be evaluated before then, else
36             # - items must be evaluated before additionalItems
37             # - in-place applicators (allOf, anyOf, oneOf, not, if/then/else, dependentSchemas) and items,
38             # additionalItems must be evaluated before unevaluatedItems (in the Unevaluated vocabulary)
39             # - properties and patternProperties must be evaluated before additionalProperties
40             # - in-place applicators and properties, patternProperties, additionalProperties must be evaluated
41             # before unevaluatedProperties (in the Unevaluated vocabulary)
42             # - contains must be evaluated before maxContains, minContains (in the Validator vocabulary)
43 92     92 0 253 sub keywords ($self, $spec_version) {
  92         208  
  92         181  
  92         201  
44             return (
45 92 100       8210 qw(allOf anyOf oneOf not if then else),
    100          
    100          
    100          
46             $spec_version eq 'draft7' ? 'dependencies' : 'dependentSchemas',
47             $spec_version !~ qr/^draft(7|2019-09)$/ ? 'prefixItems' : (),
48             'items',
49             $spec_version =~ qr/^draft(7|2019-09)$/ ? 'additionalItems' : (),
50             qw(contains properties patternProperties additionalProperties propertyNames),
51             $spec_version eq 'draft2019-09' ? qw(unevaluatedItems unevaluatedProperties) : (),
52             );
53             }
54              
55             # in draft2019-09, the unevaluated keywords were part of the Applicator vocabulary
56             foreach my $phase (qw(traverse eval)) {
57             foreach my $type (qw(Items Properties)) {
58             my $method = '_'.$phase.'_keyword_unevaluated'.$type;
59             Sub::Install::install_sub({
60             as => $method,
61             code => sub {
62 1424     1424   19199 shift;
63 1424         7189 JSON::Schema::Modern::Vocabulary::Unevaluated->$method(@_);
64             }
65             }),
66             }
67             }
68              
69 747     747   3970 sub _traverse_keyword_allOf { shift->traverse_array_schemas(@_) }
70              
71 897     897   8084 sub _eval_keyword_allOf ($self, $data, $schema, $state) {
  897         1938  
  897         1806  
  897         1780  
  897         1703  
  897         1541  
72 897         1772 my @invalid;
73 897         3679 foreach my $idx (0 .. $schema->{allOf}->$#*) {
74 2185 100       37566 if ($self->eval($data, $schema->{allOf}[$idx], +{ %$state,
75             schema_path => $state->{schema_path}.'/allOf/'.$idx })) {
76             }
77             else {
78 443         1470 push @invalid, $idx;
79 443 100       3194 last if $state->{short_circuit};
80             }
81             }
82              
83 894 100       5710 return 1 if @invalid == 0;
84              
85 392         1118 my $pl = @invalid > 1;
86 392 100       2592 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
87             }
88              
89 558     558   2749 sub _traverse_keyword_anyOf { shift->traverse_array_schemas(@_) }
90              
91 649     649   1351 sub _eval_keyword_anyOf ($self, $data, $schema, $state) {
  649         1387  
  649         1376  
  649         1207  
  649         1184  
  649         1132  
92 649         1276 my $valid = 0;
93 649         1283 my @errors;
94 649         2601 foreach my $idx (0 .. $schema->{anyOf}->$#*) {
95             next if not $self->eval($data, $schema->{anyOf}[$idx],
96 1174 100       20473 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
97 486         3711 ++$valid;
98 486 100       1942 last if $state->{short_circuit};
99             }
100              
101 648 100       4078 return 1 if $valid;
102 223         874 push $state->{errors}->@*, @errors;
103 223         846 return E($state, 'no subschemas are valid');
104             }
105              
106 542     542   2542 sub _traverse_keyword_oneOf { shift->traverse_array_schemas(@_) }
107              
108 520     520   11923 sub _eval_keyword_oneOf ($self, $data, $schema, $state) {
  520         1050  
  520         1222  
  520         1041  
  520         947  
  520         919  
109 520         1088 my (@valid, @errors);
110 520         1904 foreach my $idx (0 .. $schema->{oneOf}->$#*) {
111             next if not $self->eval($data, $schema->{oneOf}[$idx],
112 1167 100       19211 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
113 475         3724 push @valid, $idx;
114 475 100 100     2175 last if @valid > 1 and $state->{short_circuit};
115             }
116              
117 520 100       3449 return 1 if @valid == 1;
118              
119 264 100       882 if (not @valid) {
120 156         537 push $state->{errors}->@*, @errors;
121 156         594 return E($state, 'no subschemas are valid');
122             }
123             else {
124 108         667 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
125             }
126             }
127              
128 172     172   850 sub _traverse_keyword_not { shift->traverse_subschema(@_) }
129              
130 153     153   12987 sub _eval_keyword_not ($self, $data, $schema, $state) {
  153         310  
  153         313  
  153         284  
  153         281  
  153         268  
131             return 1 if not $self->eval($data, $schema->{not},
132 153 100       2518 +{ %$state, schema_path => $state->{schema_path}.'/not',
133             short_circuit_suggested => 1, # errors do not propagate upward from this subschema
134             collect_annotations => 0, # nor do annotations
135             errors => [] });
136              
137 103         1058 return E($state, 'subschema is valid');
138             }
139              
140 399     399   1909 sub _traverse_keyword_if { shift->traverse_subschema(@_) }
141 320     320   1366 sub _traverse_keyword_then { shift->traverse_subschema(@_) }
142 269     269   1085 sub _traverse_keyword_else { shift->traverse_subschema(@_) }
143              
144 357     357   728 sub _eval_keyword_if ($self, $data, $schema, $state) {
  357         748  
  357         706  
  357         618  
  357         633  
  357         639  
145             return 1 if not exists $schema->{then} and not exists $schema->{else}
146 357 100 100     1567 and not $state->{collect_annotations};
      100        
147             my $keyword = $self->eval($data, $schema->{if},
148             +{ %$state, schema_path => $state->{schema_path}.'/if',
149             short_circuit_suggested => !$state->{collect_annotations},
150 324 100       5811 errors => [],
151             })
152             ? 'then' : 'else';
153              
154 324 100       3305 return 1 if not exists $schema->{$keyword};
155             return 1 if $self->eval($data, $schema->{$keyword},
156 258 100       4035 +{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
157 98         1779 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
158             }
159              
160 325     325   1579 sub _traverse_keyword_dependentSchemas { shift->traverse_object_schemas(@_) }
161              
162 325     325   629 sub _eval_keyword_dependentSchemas ($self, $data, $schema, $state) {
  325         626  
  325         619  
  325         634  
  325         550  
  325         534  
163 325 100       1061 return 1 if not is_type('object', $data);
164              
165 197         534 my $valid = 1;
166 197         775 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
167 223 100       713 next if not exists $data->{$property};
168              
169 121 100       882 if ($self->eval($data, $schema->{dependentSchemas}{$property},
170             +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) })) {
171 18         214 next;
172             }
173              
174 103         725 $valid = 0;
175 103 100       406 last if $state->{short_circuit};
176             }
177              
178 197 100       694 return E($state, 'not all dependencies are satisfied') if not $valid;
179 94         326 return 1;
180             }
181              
182 182     182   327 sub _traverse_keyword_dependencies ($self, $schema, $state) {
  182         517  
  182         295  
  182         270  
  182         329  
183 182 50       567 return if not assert_keyword_type($state, $schema, 'object');
184              
185 182         331 my $valid = 1;
186 182         705 foreach my $property (sort keys $schema->{dependencies}->%*) {
187 232 100       605 if (is_type('array', $schema->{dependencies}{$property})) {
188             # as in dependentRequired
189              
190 61         145 foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
191             $valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
192 69 50       133 if not is_type('string', $schema->{dependencies}{$property}[$index]);
193             }
194              
195             $valid = E({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
196 61 50       151 if not is_elements_unique($schema->{dependencies}{$property});
197             }
198             else {
199             # as in dependentSchemas
200 171 50       706 $valid = 0 if not $self->traverse_property_schema($schema, $state, $property);
201             }
202             }
203 182         575 return $valid;
204             }
205              
206 182     182   326 sub _eval_keyword_dependencies ($self, $data, $schema, $state) {
  182         324  
  182         283  
  182         296  
  182         276  
  182         287  
207 182 100       551 return 1 if not is_type('object', $data);
208              
209 121         249 my $valid = 1;
210 121         467 foreach my $property (sort keys $schema->{dependencies}->%*) {
211 167 100       410 next if not exists $data->{$property};
212              
213 81 100       246 if (is_type('array', $schema->{dependencies}{$property})) {
214             # as in dependentRequired
215 23 100       120 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
216 13 100       171 $valid = E({ %$state, _schema_path_suffix => $property },
217             'object is missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
218             }
219             }
220             else {
221             # as in dependentSchemas
222 58 100       391 if ($self->eval($data, $schema->{dependencies}{$property},
223             +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) })) {
224 9         85 next;
225             }
226              
227 49         318 $valid = 0;
228 49 100       218 last if $state->{short_circuit};
229             }
230             }
231              
232 121 100       391 return E($state, 'not all dependencies are satisfied') if not $valid;
233 60         196 return 1;
234             }
235              
236 411     411   1902 sub _traverse_keyword_prefixItems { shift->traverse_array_schemas(@_) }
237              
238 427     427   17229 sub _eval_keyword_prefixItems { goto \&_eval_keyword__items_array_schemas }
239              
240 1299     1299   2368 sub _traverse_keyword_items ($self, $schema, $state) {
  1299         2352  
  1299         2369  
  1299         2112  
  1299         2183  
241 1299 100       3879 if (is_plain_arrayref($schema->{items})) {
242             return E($state, 'array form of "items" not supported in %s', $state->{spec_version})
243 658 100       3878 if $state->{spec_version} !~ /^draft(?:7|2019-09)$/;
244              
245 656         3365 return $self->traverse_array_schemas($schema, $state);
246             }
247              
248 641         2848 $self->traverse_subschema($schema, $state);
249             }
250              
251 1460     1460   23884 sub _eval_keyword_items ($self, $data, $schema, $state) {
  1460         2707  
  1460         2521  
  1460         2507  
  1460         2546  
  1460         2403  
252 1460 100       6468 goto \&_eval_keyword__items_array_schemas if is_plain_arrayref($schema->{items});
253 770         3525 goto \&_eval_keyword__items_schema;
254             }
255              
256 179     179   876 sub _traverse_keyword_additionalItems { shift->traverse_subschema(@_) }
257              
258 214     214   4653 sub _eval_keyword_additionalItems ($self, $data, $schema, $state) {
  214         400  
  214         400  
  214         399  
  214         358  
  214         359  
259 214 100       690 return 1 if not exists $state->{_last_items_index};
260 181         795 goto \&_eval_keyword__items_schema;
261             }
262              
263             # prefixItems (draft 2020-12), array-based items (all drafts)
264 1117     1117   2197 sub _eval_keyword__items_array_schemas ($self, $data, $schema, $state) {
  1117         2063  
  1117         1997  
  1117         1822  
  1117         1828  
  1117         1765  
265 1117 100       3781 return 1 if not is_type('array', $data);
266 900 100 50     5090 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
267              
268 777         1584 my $valid = 1;
269              
270 777         2541 foreach my $idx (0 .. $data->$#*) {
271 1585 100       6270 last if $idx > $schema->{$state->{keyword}}->$#*;
272 1299         2803 $state->{_last_items_index} = $idx;
273              
274 1299 100       4139 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
    100          
275 302 100       4012 next if $schema->{$state->{keyword}}[$idx];
276             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
277 111         2877 _schema_path_suffix => $idx, collect_annotations => $state->{collect_annotations} & ~1 },
278             'item not permitted');
279             }
280             elsif ($self->eval($data->[$idx], $schema->{$state->{keyword}}[$idx],
281             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
282             schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx,
283             collect_annotations => $state->{collect_annotations} & ~1 })) {
284 922         6069 next;
285             }
286              
287 186         1197 $valid = 0;
288             last if $state->{short_circuit} and not exists $schema->{
289             $state->{keyword} eq 'prefixItems' ? 'items'
290 186 50 100     1470 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
291             };
292             }
293              
294 777 100       4649 A($state, $state->{_last_items_index} == $data->$#* ? true : $state->{_last_items_index});
295 777 100       2366 return E($state, 'not all items are valid') if not $valid;
296 594         2122 return 1;
297             }
298              
299             # schema-based items (all drafts), and additionalItems (up to and including draft2019-09)
300 951     951   1925 sub _eval_keyword__items_schema ($self, $data, $schema, $state) {
  951         1752  
  951         1672  
  951         1584  
  951         1556  
  951         1446  
301 951 100       3113 return 1 if not is_type('array', $data);
302 795 100 100     4649 return 1 if ($state->{_last_items_index}//-1) == $data->$#*;
303              
304 553         1120 my $valid = 1;
305              
306 553   100     3044 foreach my $idx (($state->{_last_items_index}//-1)+1 .. $data->$#*) {
307 866 100       2950 if (is_type('boolean', $schema->{$state->{keyword}})) {
308 114 100       1596 next if $schema->{$state->{keyword}};
309             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
310             '%sitem not permitted',
311 80 100 100     2095 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
312             }
313             else {
314 752 100       17396 if ($self->eval($data->[$idx], $schema->{$state->{keyword}},
315             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
316             schema_path => $state->{schema_path}.'/'.$state->{keyword},
317             collect_annotations => $state->{collect_annotations} & ~1 })) {
318 509         3337 next;
319             }
320              
321 238         1862 $valid = 0;
322             }
323 318 100       1530 last if $state->{short_circuit};
324             }
325              
326 548         1719 $state->{_last_items_index} = $data->$#*;
327              
328 548         2194 A($state, true);
329             return E($state, 'subschema is not valid against all %sitems',
330 548 100       2385 $state->{keyword} eq 'additionalItems' ? 'additional ' : '') if not $valid;
    100          
331 275         908 return 1;
332             }
333              
334 753     753   3324 sub _traverse_keyword_contains { shift->traverse_subschema(@_) }
335              
336 723     723   1391 sub _eval_keyword_contains ($self, $data, $schema, $state) {
  723         1299  
  723         1253  
  723         1197  
  723         1409  
  723         1137  
337 723 100       2510 return 1 if not is_type('array', $data);
338              
339 506         1451 $state->{_num_contains} = 0;
340 506         1006 my (@errors, @valid);
341              
342 506         1642 foreach my $idx (0 .. $data->$#*) {
343 686 100       12021 if ($self->eval($data->[$idx], $schema->{contains},
344             +{ %$state, errors => \@errors,
345             data_path => $state->{data_path}.'/'.$idx,
346             schema_path => $state->{schema_path}.'/contains',
347             collect_annotations => $state->{collect_annotations} & ~1 })) {
348 411         2539 ++$state->{_num_contains};
349 411         1032 push @valid, $idx;
350              
351             last if $state->{short_circuit}
352             and (not exists $schema->{maxContains} or $state->{_num_contains} > $schema->{maxContains})
353 411 100 100     4020 and ($state->{_num_contains} >= ($schema->{minContains}//1));
      100        
      100        
      100        
354             }
355             }
356              
357             # note: no items contained is only valid when minContains is explicitly 0
358 506 100 66     2988 if (not $state->{_num_contains}
      66        
359             and (($schema->{minContains}//1) > 0 or $state->{spec_version} eq 'draft7')) {
360 200         593 push $state->{errors}->@*, @errors;
361 200         694 return E($state, 'subschema is not valid against any item');
362             }
363              
364 306 100       2497 return $state->{spec_version} =~ /^draft(?:7|2019-09)$/ ? 1
    100          
365             : A($state, @valid == @$data ? true : \@valid);
366             }
367              
368 2946     2946   13025 sub _traverse_keyword_properties { shift->traverse_object_schemas(@_) }
369              
370 4223     4223   27419 sub _eval_keyword_properties ($self, $data, $schema, $state) {
  4223         7939  
  4223         7573  
  4223         7302  
  4223         6866  
  4223         6536  
371 4223 100       13395 return 1 if not is_type('object', $data);
372              
373 3952         8535 my $valid = 1;
374 3952         7372 my @properties;
375 3952         20774 foreach my $property (sort keys $schema->{properties}->%*) {
376 14185 100       31586 next if not exists $data->{$property};
377 2445         5991 push @properties, $property;
378              
379 2445 100       7576 if (is_type('boolean', $schema->{properties}{$property})) {
380 408 100       5951 next if $schema->{properties}{$property};
381 114         1610 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
382             _schema_path_suffix => $property }, 'property not permitted');
383             }
384             else {
385 2037 100       25230 if ($self->eval($data->{$property}, $schema->{properties}{$property},
386             +{ %$state, data_path => jsonp($state->{data_path}, $property),
387             schema_path => jsonp($state->{schema_path}, 'properties', $property),
388             collect_annotations => $state->{collect_annotations} & ~1 })) {
389 1438         10241 next;
390             }
391              
392 596         4252 $valid = 0;
393             }
394 710 100       3320 last if $state->{short_circuit};
395             }
396              
397 3949         19245 A($state, \@properties);
398 3949 100       12179 return E($state, 'not all properties are valid') if not $valid;
399 3266         11407 return 1;
400             }
401              
402 842     842   1663 sub _traverse_keyword_patternProperties ($self, $schema, $state) {
  842         1795  
  842         1544  
  842         1503  
  842         1434  
403 842 50       2679 return if not assert_keyword_type($state, $schema, 'object');
404              
405 842         1869 my $valid = 1;
406 842         3838 foreach my $property (sort keys $schema->{patternProperties}->%*) {
407 1303 100       13268 $valid = 0 if not assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
408 1303 50       8564 $valid = 0 if not $self->traverse_property_schema($schema, $state, $property);
409             }
410 842         2928 return $valid;
411             }
412              
413 832     832   19492 sub _eval_keyword_patternProperties ($self, $data, $schema, $state) {
  832         1590  
  832         1617  
  832         1880  
  832         1546  
  832         1465  
414 832 100       2674 return 1 if not is_type('object', $data);
415              
416 635         1571 my $valid = 1;
417 635         1178 my @properties;
418 635         2728 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
419 927         13778 foreach my $property (sort grep m/(?:$property_pattern)/, keys %$data) {
420 573         2635 push @properties, $property;
421 573 100       1963 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
422 326 100       4864 next if $schema->{patternProperties}{$property_pattern};
423 112         1681 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
424             _schema_path_suffix => $property_pattern }, 'property not permitted');
425             }
426             else {
427 247 100       3105 if ($self->eval($data->{$property}, $schema->{patternProperties}{$property_pattern},
428             +{ %$state, data_path => jsonp($state->{data_path}, $property),
429             schema_path => jsonp($state->{schema_path}, 'patternProperties', $property_pattern),
430             collect_annotations => $state->{collect_annotations} & ~1 })) {
431 156         1202 next;
432             }
433              
434 91         615 $valid = 0;
435             }
436 203 100       1414 last if $state->{short_circuit};
437             }
438             }
439              
440 635         5745 A($state, [ uniqstr @properties ]);
441 635 100       2319 return E($state, 'not all properties are valid') if not $valid;
442 450         1734 return 1;
443             }
444              
445 958     958   4767 sub _traverse_keyword_additionalProperties { shift->traverse_subschema(@_) }
446              
447 1021     1021   18090 sub _eval_keyword_additionalProperties ($self, $data, $schema, $state) {
  1021         1937  
  1021         1974  
  1021         1836  
  1021         1795  
  1021         1654  
448 1021 100       3114 return 1 if not is_type('object', $data);
449              
450 747         1870 my $valid = 1;
451 747         1472 my @properties;
452 747         2695 foreach my $property (sort keys %$data) {
453 774 100 100     2757 next if exists $schema->{properties} and exists $schema->{properties}{$property};
454             next if exists $schema->{patternProperties}
455 628 100 100 150   3263 and any { $property =~ /(?:$_)/ } keys $schema->{patternProperties}->%*;
  150         1817  
456              
457 539         1547 push @properties, $property;
458 539 100       1816 if (is_type('boolean', $schema->{additionalProperties})) {
459 197 100       2875 next if $schema->{additionalProperties};
460 180         2674 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
461             'additional property not permitted');
462             }
463             else {
464 342 100       4608 if ($self->eval($data->{$property}, $schema->{additionalProperties},
465             +{ %$state, data_path => jsonp($state->{data_path}, $property),
466             schema_path => $state->{schema_path}.'/additionalProperties',
467             collect_annotations => $state->{collect_annotations} & ~1 })) {
468 185         1515 next;
469             }
470              
471 157         1189 $valid = 0;
472             }
473 337 100       2255 last if $state->{short_circuit};
474             }
475              
476 747         4283 A($state, \@properties);
477 747 100       2715 return E($state, 'not all additional properties are valid') if not $valid;
478 411         1528 return 1;
479             }
480              
481 455     455   2156 sub _traverse_keyword_propertyNames { shift->traverse_subschema(@_) }
482              
483 430     430   829 sub _eval_keyword_propertyNames ($self, $data, $schema, $state) {
  430         850  
  430         841  
  430         778  
  430         990  
  430         804  
484 430 100       1405 return 1 if not is_type('object', $data);
485              
486 251         656 my $valid = 1;
487 251         904 foreach my $property (sort keys %$data) {
488 182 100       1376 if ($self->eval($property, $schema->{propertyNames},
489             +{ %$state, data_path => jsonp($state->{data_path}, $property),
490             schema_path => $state->{schema_path}.'/propertyNames',
491             collect_annotations => $state->{collect_annotations} & ~1 })) {
492 73         591 next;
493             }
494              
495 109         752 $valid = 0;
496 109 100       463 last if $state->{short_circuit};
497             }
498              
499 251 100       962 return E($state, 'not all property names are valid') if not $valid;
500 142         505 return 1;
501             }
502              
503             1;
504              
505             __END__
506              
507             =pod
508              
509             =encoding UTF-8
510              
511             =head1 NAME
512              
513             JSON::Schema::Modern::Vocabulary::Applicator - Implementation of the JSON Schema Applicator vocabulary
514              
515             =head1 VERSION
516              
517             version 0.572
518              
519             =head1 DESCRIPTION
520              
521             =for Pod::Coverage vocabulary evaluation_order keywords
522              
523             =for stopwords metaschema
524              
525             Implementation of the JSON Schema Draft 2020-12 "Applicator" vocabulary, indicated in metaschemas
526             with the URI C<https://json-schema.org/draft/2020-12/vocab/applicator> and formally specified in
527             L<https://json-schema.org/draft/2020-12/json-schema-core.html#section-10>.
528              
529             Support is also provided for
530              
531             =over 4
532              
533             =item *
534              
535             the equivalent Draft 2019-09 keywords, indicated in metaschemas with the URI C<https://json-schema.org/draft/2019-09/vocab/applicator> and formally specified in L<https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02#section-9> (except for the C<unevaluatedItems> and C<unevaluatedProperties> keywords, which are implemented in L<JSON::Schema::Modern::Vocabulary::Unevaluated>);
536              
537             =item *
538              
539             the equivalent Draft 7 keywords that correspond to this vocabulary and are formally specified in L<https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6>.
540              
541             =back
542              
543             =for stopwords OpenAPI
544              
545             =head1 SUPPORT
546              
547             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Modern/issues>.
548              
549             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
550              
551             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
552             server|https://open-api.slack.com>, which are also great resources for finding help.
553              
554             =head1 AUTHOR
555              
556             Karen Etheridge <ether@cpan.org>
557              
558             =head1 COPYRIGHT AND LICENCE
559              
560             This software is copyright (c) 2020 by Karen Etheridge.
561              
562             This is free software; you can redistribute it and/or modify it under
563             the same terms as the Perl 5 programming language system itself.
564              
565             =cut