File Coverage

blib/lib/JSON/Schema/Tiny.pm
Criterion Covered Total %
statement 857 863 99.3
branch 494 546 90.4
condition 251 308 81.4
subroutine 97 98 98.9
pod 1 17 5.8
total 1700 1832 92.7


line stmt bran cond sub pod time code
1 16     16   5155998 use strictures 2;
  16         288  
  16         849  
2             package JSON::Schema::Tiny; # git description: v0.019-2-gecd0a20
3             # vim: set ts=8 sts=2 sw=2 tw=100 et :
4             # ABSTRACT: Validate data against a schema, minimally
5             # KEYWORDS: JSON Schema data validation structure specification tiny
6              
7             our $VERSION = '0.020';
8              
9 16     16   4357 use 5.020; # for unicode_strings, signatures, postderef features
  16         63  
10 16     16   123 use experimental 0.026 qw(signatures postderef args_array_with_signatures);
  16         344  
  16         182  
11 16     16   4353 no if "$]" >= 5.031009, feature => 'indirect';
  16         64  
  16         176  
12 16     16   861 no if "$]" >= 5.033001, feature => 'multidimensional';
  16         55  
  16         108  
13 16     16   775 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  16         36  
  16         84  
14 16     16   706 use B;
  16         53  
  16         1215  
15 16     16   5330 use Ref::Util 0.100 qw(is_plain_arrayref is_plain_hashref is_ref is_plain_arrayref);
  16         16969  
  16         1223  
16 16     16   8285 use Mojo::URL;
  16         2905489  
  16         123  
17 16     16   8087 use Mojo::JSON::Pointer;
  16         9774  
  16         111  
18 16     16   757 use Carp qw(croak carp);
  16         47  
  16         983  
19 16     16   9908 use Storable 'dclone';
  16         49767  
  16         1284  
20 16     16   4934 use JSON::MaybeXS 1.004001 'is_bool';
  16         56209  
  16         943  
21 16     16   4966 use Feature::Compat::Try;
  16         3159  
  16         120  
22 16     16   39402 use JSON::PP ();
  16         227492  
  16         558  
23 16     16   150 use List::Util 1.33 qw(any none);
  16         341  
  16         1246  
24 16     16   125 use Scalar::Util 'blessed';
  16         44  
  16         906  
25 16     16   128 use if "$]" >= 5.022, POSIX => 'isinf';
  16         33  
  16         203  
26 16     16   32864 use namespace::clean;
  16         145262  
  16         144  
27 16     16   6188 use Exporter 5.57 'import';
  16         244  
  16         228632  
28              
29             our @EXPORT_OK = qw(evaluate);
30              
31             our $BOOLEAN_RESULT = 0;
32             our $SHORT_CIRCUIT = 0;
33             our $MAX_TRAVERSAL_DEPTH = 50;
34             our $MOJO_BOOLEANS; # deprecated; renamed to $SCALARREF_BOOLEANS
35             our $SCALARREF_BOOLEANS;
36             our $SPECIFICATION_VERSION;
37              
38             my %version_uris = (
39             'https://json-schema.org/draft/2020-12/schema' => 'draft2020-12',
40             'https://json-schema.org/draft/2019-09/schema' => 'draft2019-09',
41             'http://json-schema.org/draft-07/schema#' => 'draft7',
42             );
43              
44 18     18 0 103555 sub new ($class, %args) {
  18         39  
  18         51  
  18         32  
45 18         63 bless(\%args, $class);
46             }
47              
48             sub evaluate {
49 12231 50   12231 1 19614405 croak 'evaluate called in void context' if not defined wantarray;
50              
51 12231   66     43459 $SCALARREF_BOOLEANS = $SCALARREF_BOOLEANS // $MOJO_BOOLEANS;
52             local $BOOLEAN_RESULT = $_[0]->{boolean_result} // $BOOLEAN_RESULT,
53             local $SHORT_CIRCUIT = $_[0]->{short_circuit} // $SHORT_CIRCUIT,
54             local $MAX_TRAVERSAL_DEPTH = $_[0]->{max_traversal_depth} // $MAX_TRAVERSAL_DEPTH,
55             local $SCALARREF_BOOLEANS = $_[0]->{scalarref_booleans} // $SCALARREF_BOOLEANS // $_[0]->{mojo_booleans},
56 12231 100 33     194388 local $SPECIFICATION_VERSION = $_[0]->{specification_version} // $SPECIFICATION_VERSION,
      66        
      66        
      33        
      33        
      66        
      100        
57             shift
58             if blessed($_[0]) and blessed($_[0])->isa(__PACKAGE__);
59              
60 12231 100       34689 if (defined $SPECIFICATION_VERSION) {
61             $SPECIFICATION_VERSION = 'draft'.$SPECIFICATION_VERSION
62 12105 100 100 9   50144 if $SPECIFICATION_VERSION !~ /^draft/ and any { 'draft'.$SPECIFICATION_VERSION eq $_ } values %version_uris;
  9         33  
63              
64 12105 100   26231   66081 croak '$SPECIFICATION_VERSION value is invalid' if none { $SPECIFICATION_VERSION eq $_ } values %version_uris;
  26231         54468  
65             }
66              
67 12230 50       44909 croak 'insufficient arguments' if @_ < 2;
68 12230         26655 my ($data, $schema) = @_;
69              
70 12230   100     41449 my $state = {
71             depth => 0,
72             data_path => '',
73             traversed_schema_path => '', # the accumulated traversal path up to the last $ref traversal
74             initial_schema_uri => Mojo::URL->new, # the canonical URI as of the start or the last traversed $ref
75             schema_path => '', # the rest of the path, since the start or the last traversed $ref
76             errors => [],
77             seen => {},
78             short_circuit => $BOOLEAN_RESULT || $SHORT_CIRCUIT,
79             root_schema => $schema, # so we can do $refs within the same document
80             spec_version => $SPECIFICATION_VERSION,
81             };
82              
83 12230         191523 my $valid;
84             try {
85             $valid = _eval_subschema($data, $schema, $state)
86             }
87 12230         26216 catch ($e) {
88             if (is_plain_hashref($e)) {
89             push $state->{errors}->@*, $e;
90             }
91             else {
92             E($state, 'EXCEPTION: '.$e);
93             }
94              
95             $valid = 0;
96             }
97              
98 12230 50 66     40947 warn 'result is false but there are no errors' if not $valid and not $state->{errors}->@*;
99              
100             return $BOOLEAN_RESULT ? $valid : +{
101             valid => $valid ? JSON::PP::true : JSON::PP::false,
102 12230 100       49497 $valid ? () : (errors => $state->{errors}),
    100          
    100          
103             };
104             }
105              
106             ######## NO PUBLIC INTERFACES FOLLOW THIS POINT ########
107              
108             # current spec version => { keyword => undef, or arrayref of alternatives }
109             my %removed_keywords = (
110             'draft7' => {
111             id => [ '$id' ],
112             },
113             'draft2019-09' => {
114             id => [ '$id' ],
115             definitions => [ '$defs' ],
116             dependencies => [ qw(dependentSchemas dependentRequired) ],
117             },
118             'draft2020-12' => {
119             id => [ '$id' ],
120             definitions => [ '$defs' ],
121             dependencies => [ qw(dependentSchemas dependentRequired) ],
122             '$recursiveAnchor' => [ '$dynamicAnchor' ],
123             '$recursiveRef' => [ '$dynamicRef' ],
124             additionalItems => [ 'items' ],
125             },
126             );
127              
128 20730     20730   31525 sub _eval_subschema ($data, $schema, $state) {
  20730         31544  
  20730         29864  
  20730         26829  
  20730         26134  
129 20730 50       44164 croak '_eval_subschema called in void context' if not defined wantarray;
130              
131             # do not propagate upwards changes to depth, traversed paths,
132             # but additions to errors are by reference and will be retained
133 20730         115316 $state = { %$state };
134 20730         141297 delete $state->@{'keyword', grep /^_/, keys %$state};
135              
136             abort($state, 'EXCEPTION: maximum evaluation depth exceeded')
137 20730 100       64618 if $state->{depth}++ > $MAX_TRAVERSAL_DEPTH;
138              
139             # find all schema locations in effect at this data path + canonical_uri combination
140             # if any of them are absolute prefix of this schema location, we are in a loop.
141 20727         43433 my $canonical_uri = canonical_uri($state);
142 20727         48251 my $schema_location = $state->{traversed_schema_path}.$state->{schema_path};
143             abort($state, 'EXCEPTION: infinite loop detected (same location evaluated twice)')
144             if grep substr($schema_location, 0, length) eq $_,
145 20727 100       82653 keys $state->{seen}{$state->{data_path}}{$canonical_uri}->%*;
146 20725         3618730 $state->{seen}{$state->{data_path}}{$canonical_uri}{$schema_location}++;
147              
148 20725         2564323 my $schema_type = get_type($schema);
149 20725 100 66     55331 return $schema || E($state, 'subschema is false') if $schema_type eq 'boolean';
150 19937 100       39932 abort($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object';
151              
152 19918 100       47771 return 1 if not keys %$schema;
153              
154 19667         30802 my $valid = 1;
155 19667   100     46874 my $spec_version = $state->{spec_version}//'';
156              
157 19667 100 100     648226 foreach my $keyword (
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
158             # CORE KEYWORDS
159             qw($id $schema),
160             !$spec_version || $spec_version ne 'draft7' ? '$anchor' : (),
161             !$spec_version || $spec_version eq 'draft2019-09' ? '$recursiveAnchor' : (),
162             !$spec_version || $spec_version eq 'draft2020-12' ? '$dynamicAnchor' : (),
163             '$ref',
164             !$spec_version || $spec_version eq 'draft2019-09' ? '$recursiveRef' : (),
165             !$spec_version || $spec_version eq 'draft2020-12' ? '$dynamicRef' : (),
166             !$spec_version || $spec_version ne 'draft7' ? qw($vocabulary $comment) : (),
167             !$spec_version || $spec_version eq 'draft7' ? 'definitions' : (),
168             !$spec_version || $spec_version ne 'draft7' ? '$defs' : (),
169             # APPLICATOR KEYWORDS
170             qw(allOf anyOf oneOf not if),
171             !$spec_version || $spec_version ne 'draft7' ? 'dependentSchemas' : (),
172             !$spec_version || $spec_version eq 'draft7' ? 'dependencies' : (),
173             !$spec_version || $spec_version !~ qr/^draft(7|2019-09)$/ ? 'prefixItems' : (),
174             'items',
175             !$spec_version || $spec_version =~ qr/^draft(?:7|2019-09)$/ ? 'additionalItems' : (),
176             qw(contains properties patternProperties additionalProperties propertyNames),
177             # UNEVALUATED KEYWORDS
178             !$spec_version || $spec_version ne 'draft7' ? qw(unevaluatedItems unevaluatedProperties) : (),
179             # VALIDATOR KEYWORDS
180             qw(type enum const
181             multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
182             maxLength minLength pattern
183             maxItems minItems uniqueItems),
184             !$spec_version || $spec_version ne 'draft7' ? qw(maxContains minContains) : (),
185             qw(maxProperties minProperties required),
186             !$spec_version || $spec_version ne 'draft7' ? 'dependentRequired' : (),
187             ) {
188 686038 100       1217000 next if not exists $schema->{$keyword};
189              
190             # keywords adjacent to $ref (except for definitions) are not evaluated before draft2019-09
191             next if $keyword ne '$ref' and $keyword ne 'definitions'
192 32645 100 100     145913 and exists $schema->{'$ref'} and $spec_version eq 'draft7';
      100        
      100        
193              
194 32630         60432 $state->{keyword} = $keyword;
195 32630         56313 my $error_count = $state->{errors}->@*;
196              
197 32630         196091 my $sub = __PACKAGE__->can('_eval_keyword_'.($keyword =~ s/^\$//r));
198 32630 100       88994 if (not $sub->($data, $schema, $state)) {
199             warn 'result is false but there are no errors (keyword: '.$keyword.')'
200 7145 50       18677 if $error_count == $state->{errors}->@*;
201 7145         11453 $valid = 0;
202             }
203              
204 30114 100 100     145702 last if not $valid and $state->{short_circuit};
205             }
206              
207             # check for previously-supported but now removed keywords
208 17151         96461 foreach my $keyword (sort keys $removed_keywords{$spec_version}->%*) {
209 59868 100       128119 next if not exists $schema->{$keyword};
210 214         668 my $message ='no-longer-supported "'.$keyword.'" keyword present (at location "'
211             .canonical_uri($state).'")';
212 214 50       33652 if (my $alternates = $removed_keywords{$spec_version}->{$keyword}) {
213 214         1029 my @list = map '"'.$_.'"', @$alternates;
214 214 50       574 @list = ((map $_.',', @list[0..$#list-1]), $list[-1]) if @list > 2;
215 214 100       625 splice(@list, -1, 0, 'or') if @list > 1;
216 214         685 $message .= ': this should be rewritten as '.join(' ', @list);
217             }
218 214         19248 carp $message;
219             }
220              
221 17151         131911 return $valid;
222             }
223              
224             # KEYWORD IMPLEMENTATIONS
225              
226 5212     5212   7396 sub _eval_keyword_schema ($data, $schema, $state) {
  5212         7813  
  5212         6784  
  5212         6782  
  5212         6723  
227 5212         13704 assert_keyword_type($state, $schema, 'string');
228 5212         15038 assert_uri($state, $schema);
229              
230             return abort($state, '$schema can only appear at the schema resource root')
231 5212 100       14630 if length($state->{schema_path});
232              
233 5211         12309 my $spec_version = $version_uris{$schema->{'$schema'}};
234 5211 100       11042 abort($state, 'custom $schema URIs are not supported (must be one of: %s',
235             join(', ', map '"'.$_.'"', keys %version_uris))
236             if not $spec_version;
237              
238 5182 100 100     17941 abort($state, '"$schema" indicates a different version than that requested by $JSON::Schema::Tiny::SPECIFICATION_VERSION')
239             if defined $SPECIFICATION_VERSION and $SPECIFICATION_VERSION ne $spec_version;
240              
241             # we special-case this because the check in _eval for older drafts + $ref has already happened
242             abort($state, '$schema and $ref cannot be used together in older drafts')
243 5181 100 100     12157 if exists $schema->{'$ref'} and $spec_version eq 'draft7';
244              
245 5180         15087 $state->{spec_version} = $spec_version;
246             }
247              
248 1633     1633   2590 sub _eval_keyword_ref ($data, $schema, $state) {
  1633         2735  
  1633         2335  
  1633         2310  
  1633         2241  
249 1633         4600 assert_keyword_type($state, $schema, 'string');
250 1633         4858 assert_uri_reference($state, $schema);
251              
252 1633         6853 my $uri = Mojo::URL->new($schema->{$state->{keyword}})->to_abs($state->{initial_schema_uri});
253             abort($state, '%ss to anchors are not supported', $state->{keyword})
254 1633 100 100     676044 if ($uri->fragment//'') !~ m{^(/(?:[^~]|~[01])*|)$};
255              
256             # the base of the $ref uri must be the same as the base of the root schema
257             # unfortunately this means that many uses of $ref won't work, because we don't
258             # track the locations of $ids in this or other documents.
259             abort($state, 'only same-document, same-base JSON pointers are supported in %s', $state->{keyword})
260 1539 100 100     19134 if $uri->clone->fragment(undef) ne Mojo::URL->new($state->{root_schema}{'$id'}//'');
261              
262 1060   100     574447 my $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment//'');
263 1060 100       49173 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
264              
265             return _eval_subschema($data, $subschema,
266             +{ %$state,
267             traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/'.$state->{keyword},
268 1055         10886 initial_schema_uri => $uri,
269             schema_path => '',
270             });
271             }
272              
273 52     52   73 sub _eval_keyword_recursiveRef ($data, $schema, $state) {
  52         82  
  52         77  
  52         69  
  52         75  
274 52         150 assert_keyword_type($state, $schema, 'string');
275 52         171 assert_uri_reference($state, $schema);
276              
277 52         185 my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
278 52 50 50     22594 abort($state, '$recursiveRefs to anchors are not supported')
279             if ($uri->fragment//'') !~ m{^(/(?:[^~]|~[01])*|)$};
280              
281             # the base of the $recursiveRef uri must be the same as the base of the root schema.
282             # unfortunately this means that nearly all usecases of $recursiveRef won't work, because we don't
283             # track the locations of $ids in this or other documents.
284             abort($state, 'only same-document, same-base JSON pointers are supported in $recursiveRef')
285 52 100 100     603 if $uri->clone->fragment(undef) ne Mojo::URL->new($state->{root_schema}{'$id'}//'');
286              
287 8         4073 my $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment);
288 8 50       204 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
289              
290 8 0 33     41 if (is_type('boolean', $subschema->{'$recursiveAnchor'}) and $subschema->{'$recursiveAnchor'}) {
291             $uri = Mojo::URL->new($schema->{'$recursiveRef'})
292 0   0     0 ->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
293 0         0 $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment);
294 0 0       0 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
295             }
296              
297             return _eval_subschema($data, $subschema,
298             +{ %$state,
299 8         129 traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/$recursiveRef',
300             initial_schema_uri => $uri,
301             schema_path => '',
302             });
303             }
304              
305 8     8   26 sub _eval_keyword_dynamicRef { goto \&_eval_keyword_ref }
306              
307 594     594   902 sub _eval_keyword_id ($data, $schema, $state) {
  594         971  
  594         862  
  594         837  
  594         818  
308 594         1746 assert_keyword_type($state, $schema, 'string');
309 594         1758 assert_uri_reference($state, $schema);
310              
311 594         2216 my $uri = Mojo::URL->new($schema->{'$id'});
312              
313 594 100 100     69079 if (($state->{spec_version}//'') eq 'draft7') {
314 121 100       296 if (length($uri->fragment)) {
315 3 50       27 abort($state, '$id cannot change the base uri at the same time as declaring an anchor')
316             if length($uri->clone->fragment(undef));
317              
318 3 100       537 abort($state, '$id value does not match required syntax')
319             if $uri->fragment !~ m/^[A-Za-z][A-Za-z0-9_:.-]*$/;
320              
321 2         28 return 1;
322             }
323             }
324             else {
325 473 100       1164 abort($state, '$id value "%s" cannot have a non-empty fragment', $uri) if length $uri->fragment;
326             }
327              
328 589         3567 $uri->fragment(undef);
329 589 100       3866 return E($state, '$id cannot be empty') if not length $uri;
330              
331 565 100       95498 $state->{initial_schema_uri} = $uri->is_abs ? $uri : $uri->to_abs($state->{initial_schema_uri});
332 565         63529 $state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
333 565         1043 $state->{schema_path} = '';
334              
335 565         1798 return 1;
336             }
337              
338 12     12   25 sub _eval_keyword_anchor ($data, $schema, $state) {
  12         19  
  12         28  
  12         18  
  12         16  
339 12         36 assert_keyword_type($state, $schema, 'string');
340              
341             return 1 if
342             (!$state->{spec_version} or $state->{spec_version} eq 'draft2019-09')
343             and ($schema->{'$anchor'}//'') =~ /^[A-Za-z][A-Za-z0-9_:.-]*$/
344             or
345             (!$state->{spec_version} or $state->{spec_version} eq 'draft2020-12')
346 12 50 66     160 and ($schema->{'$anchor'}//'') =~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
      50        
      66        
      33        
      50        
      33        
      66        
347              
348 0         0 abort($state, '$anchor value does not match required syntax');
349             }
350              
351 80     80   125 sub _eval_keyword_recursiveAnchor ($data, $schema, $state) {
  80         135  
  80         116  
  80         113  
  80         102  
352 80         205 assert_keyword_type($state, $schema, 'boolean');
353 80 100 100     1033 return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
354              
355             # this is required because the location is used as the base URI for future resolution
356             # of $recursiveRef, and the fragment would be disregarded in the base
357             abort($state, '"$recursiveAnchor" keyword used without "$id"')
358 39 50       318 if not exists $schema->{'$id'};
359              
360             # record the canonical location of the current position, to be used against future resolution
361             # of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
362 39         80 $state->{recursive_anchor_uri} = canonical_uri($state);
363              
364 39         115 return 1;
365             }
366              
367 14     14   26 sub _eval_keyword_dynamicAnchor ($data, $schema, $state) {
  14         21  
  14         23  
  14         18  
  14         19  
368 14 50       31 return if not assert_keyword_type($state, $schema, 'string');
369              
370             abort($state, '$dynamicAnchor value does not match required syntax')
371 14 50       72 if $schema->{'$dynamicAnchor'} !~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
372 14         32 return 1;
373             }
374              
375 4     4   10 sub _eval_keyword_vocabulary ($data, $schema, $state) {
  4         7  
  4         8  
  4         11  
  4         7  
376 4         18 assert_keyword_type($state, $schema, 'object');
377              
378 4         38 foreach my $property (sort keys $schema->{'$vocabulary'}->%*) {
379 4         36 assert_keyword_type({ %$state, _schema_path_suffix => $property }, $schema, 'boolean');
380 4         63 assert_uri($state, undef, $property);
381             }
382              
383             abort($state, '$vocabulary can only appear at the schema resource root')
384 4 50       23 if length($state->{schema_path});
385              
386             abort($state, '$vocabulary can only appear at the document root')
387 4 50       18 if length($state->{traversed_schema_path}.$state->{schema_path});
388              
389 4         25 return 1;
390             }
391              
392 180     180   293 sub _eval_keyword_comment ($data, $schema, $state) {
  180         294  
  180         258  
  180         245  
  180         246  
393 180         466 assert_keyword_type($state, $schema, 'string');
394 180         538 return 1;
395             }
396              
397 134     134   500 sub _eval_keyword_definitions { goto \&_eval_keyword_defs }
398              
399 625     625   991 sub _eval_keyword_defs ($data, $schema, $state) {
  625         1016  
  625         1030  
  625         904  
  625         857  
400 625         1741 assert_keyword_type($state, $schema, 'object');
401 623         1691 return 1;
402             }
403              
404 3579     3579   5192 sub _eval_keyword_type ($data, $schema, $state) {
  3579         5678  
  3579         4943  
  3579         4805  
  3579         4668  
405 3579 100       7921 if (is_plain_arrayref($schema->{type})) {
406 159 50       482 abort($state, 'type array is empty') if not $schema->{type}->@*;
407 159         382 foreach my $type ($schema->{type}->@*) {
408             abort($state, 'unrecognized type "%s"', $type//'<null>')
409 336 100 50 1359   1367 if not any { ($type//'') eq $_ } qw(null boolean object array string number integer);
  1359   100     3187  
410             }
411 153 50       442 abort($state, '"type" values are not unique') if not is_elements_unique($schema->{type});
412              
413 153         318 my $type = get_type($data);
414             return 1 if any {
415 284 50 100 284   1697 $type eq $_ or ($_ eq 'number' and $type eq 'integer')
      100        
      66        
      66        
416             or ($_ eq 'boolean' and $SCALARREF_BOOLEANS and $type eq 'reference to SCALAR')
417 153 100       902 } $schema->{type}->@*;
418 89         521 return E($state, 'got %s, not one of %s', $type, join(', ', $schema->{type}->@*));
419             }
420             else {
421 3420         8849 assert_keyword_type($state, $schema, 'string');
422             abort($state, 'unrecognized type "%s"', $schema->{type}//'<null>')
423 3414 100 50 15821   19864 if not any { ($schema->{type}//'') eq $_ } qw(null boolean object array string number integer);
  15821   50     33085  
424              
425 3412         11509 my $type = get_type($data);
426             return 1 if $type eq $schema->{type} or ($schema->{type} eq 'number' and $type eq 'integer')
427 3412 100 100     21576 or ($schema->{type} eq 'boolean' and $SCALARREF_BOOLEANS and $type eq 'reference to SCALAR');
      100        
      100        
      100        
      100        
428 943         2447 return E($state, 'got %s, not %s', $type, $schema->{type});
429             }
430             }
431              
432 385     385   617 sub _eval_keyword_enum ($data, $schema, $state) {
  385         625  
  385         561  
  385         547  
  385         505  
433 385         1020 assert_keyword_type($state, $schema, 'array');
434 385 50       1127 abort($state, '"enum" values are not unique') if not is_elements_unique($schema->{enum});
435              
436 385         626 my @s; my $idx = 0;
  385         585  
437 385 100   822   2438 return 1 if any { is_equal($data, $_, $s[$idx++] = {}) } $schema->{enum}->@*;
  822         8661  
438              
439             return E($state, 'value does not match'
440             .(!(grep $_->{path}, @s) ? ''
441 176 100       1398 : ' (differences start '.join(', ', map 'from item #'.$_.' at "'.$s[$_]->{path}.'"', 0..$#s).')'));
442             }
443              
444 1009     1009   1573 sub _eval_keyword_const ($data, $schema, $state) {
  1009         1616  
  1009         1366  
  1009         1434  
  1009         1340  
445 1009 100       2608 return 1 if is_equal($data, $schema->{const}, my $s = {});
446             return E($state, 'value does not match'
447 460 100       5029 .($s->{path} ? ' (differences start at "'.$s->{path}.'")' : ''));
448             }
449              
450 822     822   1340 sub _eval_keyword_multipleOf ($data, $schema, $state) {
  822         1434  
  822         1146  
  822         1176  
  822         1088  
451 822         2124 assert_keyword_type($state, $schema, 'number');
452 822 50       2328 abort($state, 'multipleOf value is not a positive number') if $schema->{multipleOf} <= 0;
453              
454 822 100       11952 return 1 if not is_type('number', $data);
455              
456             # if either value is a float, use the bignum library for the calculation
457 634 100 100     2767 if (ref($data) =~ /^Math::Big(?:Int|Float)$/ or ref($schema->{multipleOf}) =~ /^Math::Big(?:Int|Float)$/) {
458 48 100       231 $data = ref($data) =~ /^Math::Big(?:Int|Float)$/ ? $data->copy : Math::BigFloat->new($data);
459 48 50       2541 my $divisor = ref($schema->{multipleOf}) =~ /^Math::Big(?:Int|Float)$/ ? $schema->{multipleOf} : Math::BigFloat->new($schema->{multipleOf});
460 48         154 my ($quotient, $remainder) = $data->bdiv($divisor);
461 48 50       49586 return E($state, 'overflow while calculating quotient') if $quotient->is_inf;
462 48 100       478 return 1 if $remainder == 0;
463             }
464             else {
465 586         1411 my $quotient = $data / $schema->{multipleOf};
466 586 50       3243 return E($state, 'overflow while calculating quotient')
    50          
467             if "$]" >= 5.022 ? isinf($quotient) : $quotient =~ /^-?Inf$/i;
468 586 100       2062 return 1 if int($quotient) == $quotient;
469             }
470              
471 291         4468 return E($state, 'value is not a multiple of %s', sprintf_num($schema->{multipleOf}));
472             }
473              
474 579     579   881 sub _eval_keyword_maximum ($data, $schema, $state) {
  579         941  
  579         847  
  579         842  
  579         774  
475 579         1578 assert_keyword_type($state, $schema, 'number');
476 579 100       1233 return 1 if not is_type('number', $data);
477 385 100       1332 return 1 if $data <= $schema->{maximum};
478 169         4711 return E($state, 'value is larger than %s', sprintf_num($schema->{maximum}));
479             }
480              
481 477     477   752 sub _eval_keyword_exclusiveMaximum ($data, $schema, $state) {
  477         874  
  477         698  
  477         646  
  477         669  
482 477         1205 assert_keyword_type($state, $schema, 'number');
483 477 100       1022 return 1 if not is_type('number', $data);
484 289 100       980 return 1 if $data < $schema->{exclusiveMaximum};
485 151         4781 return E($state, 'value is equal to or larger than %s', sprintf_num($schema->{exclusiveMaximum}));
486             }
487              
488 707     707   1167 sub _eval_keyword_minimum ($data, $schema, $state) {
  707         1114  
  707         1060  
  707         1005  
  707         925  
489 707         1922 assert_keyword_type($state, $schema, 'number');
490 707 100       1576 return 1 if not is_type('number', $data);
491 504 100       1727 return 1 if $data >= $schema->{minimum};
492 239         9486 return E($state, 'value is smaller than %s', sprintf_num($schema->{minimum}));
493             }
494              
495 417     417   652 sub _eval_keyword_exclusiveMinimum ($data, $schema, $state) {
  417         719  
  417         608  
  417         586  
  417         574  
496 417         1079 assert_keyword_type($state, $schema, 'number');
497 417 100       988 return 1 if not is_type('number', $data);
498 229 100       782 return 1 if $data > $schema->{exclusiveMinimum};
499 121         3632 return E($state, 'value is equal to or smaller than %s', sprintf_num($schema->{exclusiveMinimum}));
500             }
501              
502 553     553   864 sub _eval_keyword_maxLength ($data, $schema, $state) {
  553         903  
  553         789  
  553         840  
  553         760  
503 553         1480 assert_non_negative_integer($schema, $state);
504              
505 553 100       1160 return 1 if not is_type('string', $data);
506 344 100       1493 return 1 if length($data) <= $schema->{maxLength};
507 162         1855 return E($state, 'length is greater than %d', $schema->{maxLength});
508             }
509              
510 512     512   785 sub _eval_keyword_minLength ($data, $schema, $state) {
  512         865  
  512         726  
  512         721  
  512         689  
511 512         1343 assert_non_negative_integer($schema, $state);
512              
513 512 100       1032 return 1 if not is_type('string', $data);
514 302 100       1199 return 1 if length($data) >= $schema->{minLength};
515 142         1839 return E($state, 'length is less than %d', $schema->{minLength});
516             }
517              
518 894     894   1355 sub _eval_keyword_pattern ($data, $schema, $state) {
  894         1515  
  894         1242  
  894         1358  
  894         1198  
519 894         2222 assert_keyword_type($state, $schema, 'string');
520 894         2894 assert_pattern($state, $schema->{pattern});
521              
522 893 100       1788 return 1 if not is_type('string', $data);
523 666 100       4415 return 1 if $data =~ m/$schema->{pattern}/;
524 313         824 return E($state, 'pattern does not match');
525             }
526              
527 425     425   616 sub _eval_keyword_maxItems ($data, $schema, $state) {
  425         662  
  425         548  
  425         615  
  425         628  
528 425         1117 assert_non_negative_integer($schema, $state);
529              
530 425 100       850 return 1 if not is_type('array', $data);
531 256 100       843 return 1 if @$data <= $schema->{maxItems};
532 122 100       1816 return E($state, 'more than %d item%s', $schema->{maxItems}, $schema->{maxItems} > 1 ? 's' : '');
533             }
534              
535 424     424   651 sub _eval_keyword_minItems ($data, $schema, $state) {
  424         673  
  424         603  
  424         567  
  424         677  
536 424         1094 assert_non_negative_integer($schema, $state);
537              
538 424 100       840 return 1 if not is_type('array', $data);
539 257 100       820 return 1 if @$data >= $schema->{minItems};
540 124 100       1340 return E($state, 'fewer than %d item%s', $schema->{minItems}, $schema->{minItems} > 1 ? 's' : '');
541             }
542              
543 769     769   1156 sub _eval_keyword_uniqueItems ($data, $schema, $state) {
  769         1191  
  769         1078  
  769         1067  
  769         1033  
544 769         1900 assert_keyword_type($state, $schema, 'boolean');
545 769 100       8248 return 1 if not is_type('array', $data);
546 608 100       2406 return 1 if not $schema->{uniqueItems};
547 443 100       4060 return 1 if is_elements_unique($data, my $equal_indices = []);
548 201         566 return E($state, 'items at indices %d and %d are not unique', @$equal_indices);
549             }
550              
551 84     84   134 sub _eval_keyword_maxContains ($data, $schema, $state) {
  84         127  
  84         119  
  84         122  
  84         124  
552 84         226 assert_non_negative_integer($schema, $state);
553 84 100       209 return 1 if not exists $state->{_num_contains};
554 76 50       169 return 1 if not is_type('array', $data);
555              
556             return E($state, 'contains too many matching items')
557 76 100       322 if $state->{_num_contains} > $schema->{maxContains};
558              
559 44         1211 return 1;
560             }
561              
562 102     102   158 sub _eval_keyword_minContains ($data, $schema, $state) {
  102         148  
  102         142  
  102         145  
  102         145  
563 102         281 assert_non_negative_integer($schema, $state);
564 102 100       263 return 1 if not exists $state->{_num_contains};
565 94 50       196 return 1 if not is_type('array', $data);
566              
567             return E($state, 'contains too few matching items')
568 94 100       284 if $state->{_num_contains} < $schema->{minContains};
569              
570 60         1059 return 1;
571             }
572              
573 340     340   491 sub _eval_keyword_maxProperties ($data, $schema, $state) {
  340         538  
  340         483  
  340         504  
  340         440  
574 340         877 assert_non_negative_integer($schema, $state);
575              
576 340 100       647 return 1 if not is_type('object', $data);
577 202 100       814 return 1 if keys %$data <= $schema->{maxProperties};
578             return E($state, 'more than %d propert%s', $schema->{maxProperties},
579 98 100       1825 $schema->{maxProperties} > 1 ? 'ies' : 'y');
580             }
581              
582 340     340   528 sub _eval_keyword_minProperties ($data, $schema, $state) {
  340         564  
  340         474  
  340         511  
  340         446  
583 340         858 assert_non_negative_integer($schema, $state);
584              
585 340 100       772 return 1 if not is_type('object', $data);
586 202 100       868 return 1 if keys %$data >= $schema->{minProperties};
587             return E($state, 'fewer than %d propert%s', $schema->{minProperties},
588 98 100       1317 $schema->{minProperties} > 1 ? 'ies' : 'y');
589             }
590              
591 1410     1410   2019 sub _eval_keyword_required ($data, $schema, $state) {
  1410         2111  
  1410         2187  
  1410         2035  
  1410         1956  
592 1410         3343 assert_keyword_type($state, $schema, 'array');
593             abort($state, '"required" element is not a string')
594 1410 50   1596   8275 if any { !is_type('string', $_) } $schema->{required}->@*;
  1596         3424  
595 1410 50       6064 abort($state, '"required" values are not unique') if not is_elements_unique($schema->{required});
596              
597 1410 100       2577 return 1 if not is_type('object', $data);
598              
599 1260         5085 my @missing = grep !exists $data->{$_}, $schema->{required}->@*;
600 1260 100       3788 return 1 if not @missing;
601 566 100       2406 return E($state, 'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
602             }
603              
604 271     271   440 sub _eval_keyword_dependentRequired ($data, $schema, $state) {
  271         408  
  271         401  
  271         384  
  271         360  
605 271         689 assert_keyword_type($state, $schema, 'object');
606              
607 271         1214 foreach my $property (sort keys $schema->{dependentRequired}->%*) {
608             E({ %$state, _schema_path_suffix => $property }, 'value is not an array'), next
609 287 50       646 if not is_type('array', $schema->{dependentRequired}{$property});
610              
611 287         941 foreach my $index (0..$schema->{dependentRequired}{$property}->$#*) {
612             abort({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
613 301 100       684 if not is_type('string', $schema->{dependentRequired}{$property}[$index]);
614             }
615              
616             abort({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
617 286 50       745 if not is_elements_unique($schema->{dependentRequired}{$property});
618             }
619              
620 270 100       549 return 1 if not is_type('object', $data);
621              
622 173         387 my $valid = 1;
623 173         534 foreach my $property (sort keys $schema->{dependentRequired}->%*) {
624 189 100       487 next if not exists $data->{$property};
625              
626 153 100       807 if (my @missing = grep !exists($data->{$_}), $schema->{dependentRequired}{$property}->@*) {
627 79 100       857 $valid = E({ %$state, _schema_path_suffix => $property },
628             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
629             }
630             }
631              
632 173 100       556 return 1 if $valid;
633 79         230 return E($state, 'not all dependencies are satisfied');
634             }
635              
636 549     549   851 sub _eval_keyword_allOf ($data, $schema, $state) {
  549         910  
  549         806  
  549         818  
  549         740  
637 549         1478 assert_array_schemas($schema, $state);
638              
639 549         857 my @invalid;
640 549         1785 foreach my $idx (0..$schema->{allOf}->$#*) {
641             next if _eval_subschema($data, $schema->{allOf}[$idx],
642 805 100       7770 +{ %$state, schema_path => $state->{schema_path}.'/allOf/'.$idx });
643              
644 187         789 push @invalid, $idx;
645 187 100       588 last if $state->{short_circuit};
646             }
647              
648 382 100       1746 return 1 if @invalid == 0;
649              
650 154         376 my $pl = @invalid > 1;
651 154 100       697 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
652             }
653              
654 433     433   716 sub _eval_keyword_anyOf ($data, $schema, $state) {
  433         736  
  433         735  
  433         605  
  433         559  
655 433         1256 assert_array_schemas($schema, $state);
656              
657 433         734 my $valid = 0;
658 433         755 my @errors;
659 433         1383 foreach my $idx (0..$schema->{anyOf}->$#*) {
660             next if not _eval_subschema($data, $schema->{anyOf}[$idx],
661 760 100       8005 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
662 239         1486 ++$valid;
663 239 100       744 last if $state->{short_circuit};
664             }
665              
666 294 100       1070 return 1 if $valid;
667 92         278 push $state->{errors}->@*, @errors;
668 92         248 return E($state, 'no subschemas are valid');
669             }
670              
671 509     509   737 sub _eval_keyword_oneOf ($data, $schema, $state) {
  509         800  
  509         784  
  509         668  
  509         792  
672 509         1366 assert_array_schemas($schema, $state);
673              
674 509         880 my (@valid, @errors);
675 509         1591 foreach my $idx (0..$schema->{oneOf}->$#*) {
676             next if not _eval_subschema($data, $schema->{oneOf}[$idx],
677 1061 100       10710 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
678 377         2189 push @valid, $idx;
679 377 100 100     1385 last if @valid > 1 and $state->{short_circuit};
680             }
681              
682 358 100       1239 return 1 if @valid == 1;
683              
684 201 100       496 if (not @valid) {
685 123         345 push $state->{errors}->@*, @errors;
686 123         301 return E($state, 'no subschemas are valid');
687             }
688             else {
689 78         333 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
690             }
691             }
692              
693 131     131   209 sub _eval_keyword_not ($data, $schema, $state) {
  131         225  
  131         201  
  131         173  
  131         190  
694             return 1 if not _eval_subschema($data, $schema->{not},
695 131 100       1289 +{ %$state, schema_path => $state->{schema_path}.'/not', short_circuit => 1, errors => [] });
696              
697 84         577 return E($state, 'subschema is valid');
698             }
699              
700 298     298   466 sub _eval_keyword_if ($data, $schema, $state) {
  298         555  
  298         438  
  298         469  
  298         413  
701 298 100 100     891 return 1 if not exists $schema->{then} and not exists $schema->{else};
702             my $keyword = _eval_subschema($data, $schema->{if},
703 270 100       2720 +{ %$state, schema_path => $state->{schema_path}.'/if', short_circuit => 1, errors => [] })
704             ? 'then' : 'else';
705              
706 270 100       1708 return 1 if not exists $schema->{$keyword};
707             return 1 if _eval_subschema($data, $schema->{$keyword},
708 224 100       1992 +{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
709 70         701 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
710             }
711              
712 297     297   437 sub _eval_keyword_dependentSchemas ($data, $schema, $state) {
  297         482  
  297         423  
  297         446  
  297         436  
713 297         770 assert_keyword_type($state, $schema, 'object');
714              
715 297 100       757 return 1 if not is_type('object', $data);
716              
717 173         335 my $valid = 1;
718 173         689 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
719             next if not exists $data->{$property}
720             or _eval_subschema($data, $schema->{dependentSchemas}{$property},
721 199 100 100     919 +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) });
722              
723 89         384 $valid = 0;
724 89 100       358 last if $state->{short_circuit};
725             }
726              
727 173 100       636 return E($state, 'not all dependencies are satisfied') if not $valid;
728 84         218 return 1;
729             }
730              
731 178     178   632 sub _eval_keyword_dependencies ($data, $schema, $state) {
  178         291  
  178         256  
  178         245  
  178         261  
732 178         445 assert_keyword_type($state, $schema, 'object');
733              
734 178 100       424 return 1 if not is_type('object', $data);
735              
736 111         207 my $valid = 1;
737 111         420 foreach my $property (sort keys $schema->{dependencies}->%*) {
738 158 100       346 if (is_type('array', $schema->{dependencies}{$property})) {
739             # as in dependentRequired
740              
741 52         159 foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
742             $valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
743 62 50       130 if not is_type('string', $schema->{dependencies}{$property}[$index]);
744             }
745              
746             abort({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
747 52 50       131 if not is_elements_unique($schema->{dependencies}{$property});
748              
749 52 100       141 next if not exists $data->{$property};
750              
751 24 100       117 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
752 14 100       180 $valid = E({ %$state, _schema_path_suffix => $property },
753             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
754             }
755             }
756             else {
757             # as in dependentSchemas
758             next if not exists $data->{$property}
759             or _eval_subschema($data, $schema->{dependencies}{$property},
760 106 100 100     548 +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) });
761              
762 43         180 $valid = 0;
763 43 100       182 last if $state->{short_circuit};
764             }
765             }
766              
767 111 100       411 return 1 if $valid;
768 55         132 return E($state, 'not all dependencies are satisfied');
769             }
770              
771             # drafts 4, 6, 7, 2019-09:
772             # prefixItems: ignored
773             # items - array-based - start at 0; set $state->{_last_items_index} to last evaluated (not successfully).
774             # items - schema-based - start at 0; set $state->{_last_items_index} to last data item.
775             # booleans NOT accepted in draft4.
776             # additionalItems - schema-based. consume $state->{_last_items_index} as starting point.
777             # booleans accepted in all versions.
778              
779             # draft2020-12:
780             # prefixItems - array-based - start at 0; set $state->{_last_items_index} to last evaluated (not successfully).
781             # items - array-based: error
782             # items - schema-based - consume $state->{_last_items_index} as starting point.
783             # additionalItems - ignored
784              
785             # no $SPECIFICATION_VERSION specified:
786             # prefixItems - array-based - set $state->{_last_items_index} to last evaluated (not successfully).
787             # items - array-based - starting index is always 0
788             # set $state->{_last_items_index} to last evaluated (not successfully).
789             # items - schema-based - consume $state->{_last_items_index} as starting point
790             # set $state->{_last_items_index} to last data item.
791             # booleans accepted.
792             # additionalItems - schema-based. consume $state->{_last_items_index} as starting point.
793             # booleans accepted.
794              
795             # prefixItems + items(array-based): items will generate an error
796             # prefixItems + additionalItems: additionalItems will be ignored
797             # items(schema-based) + additionalItems: additionalItems does nothing.
798              
799 395     395   621 sub _eval_keyword_prefixItems ($data, $schema, $state) {
  395         683  
  395         552  
  395         558  
  395         591  
800 395 50       919 return if not assert_array_schemas($schema, $state);
801 395         1385 goto \&_eval_keyword__items_array_schemas;
802             }
803              
804 1284     1284   1895 sub _eval_keyword_items ($data, $schema, $state) {
  1284         2012  
  1284         1868  
  1284         1750  
  1284         1765  
805 1284 100       3107 if (is_plain_arrayref($schema->{items})) {
806             abort($state, 'array form of "items" not supported in %s', $state->{spec_version})
807 684 100 100     1977 if ($state->{spec_version}//'') eq 'draft2020-12';
808              
809 683         2407 goto \&_eval_keyword__items_array_schemas;
810             }
811              
812 600   100     2441 $state->{_last_items_index} //= -1;
813 600         2108 goto \&_eval_keyword__items_schema;
814             }
815              
816 215     215   289 sub _eval_keyword_additionalItems ($data, $schema, $state) {
  215         338  
  215         312  
  215         302  
  215         295  
817 215 100       527 return 1 if not exists $state->{_last_items_index};
818 183         580 goto \&_eval_keyword__items_schema;
819             }
820              
821             # prefixItems (draft 2020-12), array-based items (all drafts)
822 1078     1078   1725 sub _eval_keyword__items_array_schemas ($data, $schema, $state) {
  1078         1603  
  1078         1508  
  1078         1408  
  1078         1415  
823 1078 50       2656 abort($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
824 1078 100       2185 return 1 if not is_type('array', $data);
825              
826 865         1558 my $valid = 1;
827              
828 865         2408 foreach my $idx (0..$data->$#*) {
829 1519 100       5325 last if $idx > $schema->{$state->{keyword}}->$#*;
830 1250         2359 $state->{_last_items_index} = $idx;
831              
832 1250 100       2802 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
833 274 100       3434 next if $schema->{$state->{keyword}}[$idx];
834 108         1826 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
835             _schema_path_suffix => $idx }, 'item not permitted');
836             }
837             else {
838             next if _eval_subschema($data->[$idx], $schema->{$state->{keyword}}[$idx],
839             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
840 976 100       15896 schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx });
841             }
842              
843 175         721 $valid = 0;
844             last if $state->{short_circuit} and not exists $schema->{
845             $state->{keyword} eq 'prefixItems' ? 'items'
846 175 50 100     1262 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
847             };
848             }
849              
850 865 100       2327 return E($state, 'not all items are valid') if not $valid;
851 693         1597 return 1;
852             }
853              
854             # schema-based items (all drafts), and additionalItems (drafts 4,6,7,2019-09)
855 783     783   1171 sub _eval_keyword__items_schema ($data, $schema, $state) {
  783         1183  
  783         1072  
  783         1060  
  783         1018  
856 783 100       1677 return 1 if not is_type('array', $data);
857 679 100       2201 return 1 if $state->{_last_items_index} == $data->$#*;
858              
859 441         791 my $valid = 1;
860 441         1474 foreach my $idx ($state->{_last_items_index}+1 .. $data->$#*) {
861 667 100 100     1945 if (is_type('boolean', $schema->{$state->{keyword}})
862             and ($state->{keyword} eq 'additionalItems')) {
863 26 100       458 next if $schema->{$state->{keyword}};
864             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
865             '%sitem not permitted',
866 20 50 33     437 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
867             }
868             else {
869             next if _eval_subschema($data->[$idx], $schema->{$state->{keyword}},
870             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
871 641 100       10778 schema_path => $state->{schema_path}.'/'.$state->{keyword} });
872 216         838 $valid = 0;
873             }
874              
875 236 100       847 last if $state->{short_circuit};
876             }
877              
878 376         1096 $state->{_last_items_index} = $data->$#*;
879              
880             return E($state, 'subschema is not valid against all %sitems',
881 376 100 100     1598 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '')
    100          
882             if not $valid;
883 179         419 return 1;
884             }
885              
886 709     709   1093 sub _eval_keyword_contains ($data, $schema, $state) {
  709         1081  
  709         1035  
  709         951  
  709         952  
887 709 100       1392 return 1 if not is_type('array', $data);
888              
889 496         1186 $state->{_num_contains} = 0;
890 496         761 my @errors;
891 496         1368 foreach my $idx (0..$data->$#*) {
892 614 100       7619 if (_eval_subschema($data->[$idx], $schema->{contains},
893             +{ %$state, errors => \@errors,
894             data_path => $state->{data_path}.'/'.$idx,
895             schema_path => $state->{schema_path}.'/contains' })) {
896 385         2630 ++$state->{_num_contains};
897              
898             last if $state->{short_circuit}
899             and (not exists $schema->{maxContains} or $state->{_num_contains} > $schema->{maxContains})
900 385 100 100     2824 and ($state->{_num_contains} >= ($schema->{minContains}//1));
      100        
      100        
      100        
901             }
902             }
903              
904             # note: no items contained is only valid when minContains is explicitly 0
905 496 100 66     4456 if (not $state->{_num_contains} and (($schema->{minContains}//1) > 0
      66        
906             or $state->{spec_version} and $state->{spec_version} eq 'draft7')) {
907 195         446 push $state->{errors}->@*, @errors;
908 195         444 return E($state, 'subschema is not valid against any item');
909             }
910              
911 301         841 return 1;
912             }
913              
914 2283     2283   3519 sub _eval_keyword_properties ($data, $schema, $state) {
  2283         3439  
  2283         3162  
  2283         3448  
  2283         2958  
915 2283         5747 assert_keyword_type($state, $schema, 'object');
916 2283 100       5264 return 1 if not is_type('object', $data);
917              
918 2032         3776 my $valid = 1;
919 2032         7638 foreach my $property (sort keys $schema->{properties}->%*) {
920 2587 100       6322 next if not exists $data->{$property};
921              
922 1586 100       3506 if (is_type('boolean', $schema->{properties}{$property})) {
923 323 100       4268 next if $schema->{properties}{$property};
924 106         1301 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
925             _schema_path_suffix => $property }, 'property not permitted');
926             }
927             else {
928             next if _eval_subschema($data->{$property}, $schema->{properties}{$property},
929             +{ %$state,
930             data_path => jsonp($state->{data_path}, $property),
931 1263 100       11974 schema_path => jsonp($state->{schema_path}, 'properties', $property) });
932              
933 315         1441 $valid = 0;
934             }
935 421 100       1802 last if $state->{short_circuit};
936             }
937              
938 1885 100       6583 return E($state, 'not all properties are valid') if not $valid;
939 1486         3435 return 1;
940             }
941              
942 808     808   1231 sub _eval_keyword_patternProperties ($data, $schema, $state) {
  808         1308  
  808         1227  
  808         1126  
  808         1082  
943 808         2058 assert_keyword_type($state, $schema, 'object');
944              
945 808         3589 foreach my $property (sort keys $schema->{patternProperties}->%*) {
946 1249         8595 assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
947             }
948              
949 806 100       1859 return 1 if not is_type('object', $data);
950              
951 613         1227 my $valid = 1;
952 613         1949 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
953 897         9084 foreach my $property (sort grep m/$property_pattern/, keys %$data) {
954 556 100       2449 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
955 318 100       4210 next if $schema->{patternProperties}{$property_pattern};
956 108         1286 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
957             _schema_path_suffix => $property_pattern }, 'property not permitted');
958             }
959             else {
960             next if _eval_subschema($data->{$property}, $schema->{patternProperties}{$property_pattern},
961             +{ %$state,
962             data_path => jsonp($state->{data_path}, $property),
963 238 100       2341 schema_path => jsonp($state->{schema_path}, 'patternProperties', $property_pattern) });
964              
965 87         379 $valid = 0;
966             }
967 195 100       1046 last if $state->{short_circuit};
968             }
969             }
970              
971 613 100       3415 return E($state, 'not all properties are valid') if not $valid;
972 433         1107 return 1;
973             }
974              
975 718     718   1102 sub _eval_keyword_additionalProperties ($data, $schema, $state) {
  718         1168  
  718         1020  
  718         971  
  718         938  
976 718 100       1358 return 1 if not is_type('object', $data);
977              
978 519         1145 my $valid = 1;
979 519         1545 foreach my $property (sort keys %$data) {
980 502 100 100     1632 next if exists $schema->{properties} and exists $schema->{properties}{$property};
981             next if exists $schema->{patternProperties}
982 392 100 100 147   1698 and any { $property =~ /$_/ } keys $schema->{patternProperties}->%*;
  147         1302  
983              
984 305 100       795 if (is_type('boolean', $schema->{additionalProperties})) {
985 164 100       2161 next if $schema->{additionalProperties};
986              
987 148         1760 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
988             'additional property not permitted');
989             }
990             else {
991             next if _eval_subschema($data->{$property}, $schema->{additionalProperties},
992             +{ %$state,
993             data_path => jsonp($state->{data_path}, $property),
994 141 100       1363 schema_path => $state->{schema_path}.'/additionalProperties' });
995              
996 37         160 $valid = 0;
997             }
998 185 100       1040 last if $state->{short_circuit};
999             }
1000              
1001 467 100       2128 return E($state, 'not all additional properties are valid') if not $valid;
1002 283         651 return 1;
1003             }
1004              
1005 413     413   686 sub _eval_keyword_propertyNames ($data, $schema, $state) {
  413         641  
  413         657  
  413         608  
  413         560  
1006 413 100       946 return 1 if not is_type('object', $data);
1007              
1008 238         590 my $valid = 1;
1009 238         809 foreach my $property (sort keys %$data) {
1010             next if _eval_subschema($property, $schema->{propertyNames},
1011             +{ %$state,
1012             data_path => jsonp($state->{data_path}, $property),
1013 154 100       750 schema_path => $state->{schema_path}.'/propertyNames' });
1014              
1015 104         465 $valid = 0;
1016 104 100       364 last if $state->{short_circuit};
1017             }
1018              
1019 238 100       832 return E($state, 'not all property names are valid') if not $valid;
1020 134         360 return 1;
1021             }
1022              
1023 356     356   548 sub _eval_keyword_unevaluatedItems ($data, $schema, $state) {
  356         565  
  356         498  
  356         521  
  356         459  
1024 356         782 abort($state, 'keyword not yet supported');
1025             }
1026              
1027 549     549   848 sub _eval_keyword_unevaluatedProperties ($data, $schema, $state) {
  549         837  
  549         866  
  549         755  
  549         1071  
1028 549         1139 abort($state, 'keyword not yet supported');
1029             }
1030              
1031             # UTILITIES
1032              
1033 50485     50485 0 447793 sub is_type ($type, $value) {
  50485         70317  
  50485         69478  
  50485         63172  
1034 50485 100       93593 if ($type eq 'null') {
1035 71         306 return !(defined $value);
1036             }
1037 50414 100       88157 if ($type eq 'boolean') {
1038 5294         15836 return is_bool($value);
1039             }
1040 45120 100       82508 if ($type eq 'object') {
1041 11602         36997 return is_plain_hashref($value);
1042             }
1043 33518 100       59559 if ($type eq 'array') {
1044 8569         27957 return is_plain_arrayref($value);
1045             }
1046              
1047 24949 100 100     69780 if ($type eq 'string' or $type eq 'number' or $type eq 'integer') {
      100        
1048 24931 100       46191 return 0 if not defined $value;
1049 24913         91774 my $flags = B::svref_2object(\$value)->FLAGS;
1050              
1051 24913 100       58859 if ($type eq 'string') {
1052 15979   66     101714 return !is_ref($value) && $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK));
1053             }
1054              
1055 8934 100       18476 if ($type eq 'number') {
1056 6067   100     33784 return ref($value) =~ /^Math::Big(?:Int|Float)$/
1057             || !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK));
1058             }
1059              
1060 2867 50       5730 if ($type eq 'integer') {
1061 2867   100     23635 return ref($value) =~ /^Math::Big(?:Int|Float)$/ && $value->is_int
1062             || !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK)) && int($value) == $value;
1063             }
1064             }
1065              
1066 18 100       100 if ($type =~ /^reference to (.+)$/) {
1067 14   33     162 return !blessed($value) && ref($value) eq $1;
1068             }
1069              
1070 4         23 return ref($value) eq $type;
1071             }
1072              
1073 37328     37328 0 126224 sub get_type ($value) {
  37328         56264  
  37328         49394  
1074 37328 100       74208 return 'null' if not defined $value;
1075 37022 100       90833 return 'object' if is_plain_hashref($value);
1076 15792 100       28979 return 'array' if is_plain_arrayref($value);
1077 14543 100       31511 return 'boolean' if is_bool($value);
1078              
1079 12441 100       82824 return ref($value) =~ /^Math::Big(?:Int|Float)$/ ? ($value->is_int ? 'integer' : 'number')
    100          
    100          
    100          
1080             : (blessed($value) ? '' : 'reference to ').ref($value)
1081             if is_ref($value);
1082              
1083 11531         32663 my $flags = B::svref_2object(\$value)->FLAGS;
1084 11531 100 100     36998 return 'string' if $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK));
1085 7067 100 66     31988 return int($value) == $value ? 'integer' : 'number'
    100          
1086             if !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK));
1087              
1088 1         11 croak sprintf('ambiguous type for %s',
1089             JSON::MaybeXS->new(allow_nonref => 1, canonical => 1, utf8 => 0)->encode($value));
1090             }
1091              
1092             # compares two arbitrary data payloads for equality, as per
1093             # https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.2.2
1094             # if provided with a state hashref, any differences are recorded within
1095 6451     6451 0 8833 sub is_equal ($x, $y, $state = {}) {
  6451         9448  
  6451         8734  
  6451         9630  
  6451         8451  
1096 6451   100     25456 $state->{path} //= '';
1097              
1098 6451         13392 my @types = map get_type($_), $x, $y;
1099              
1100 6451 100       17776 if ($SCALARREF_BOOLEANS) {
1101 104 100       200 ($x, $types[0]) = (0+!!$$x, 'boolean') if $types[0] eq 'reference to SCALAR';
1102 104 100       204 ($y, $types[1]) = (0+!!$$y, 'boolean') if $types[1] eq 'reference to SCALAR';
1103             }
1104              
1105 6451 100       14203 return 0 if $types[0] ne $types[1];
1106 5313 100       9654 return 1 if $types[0] eq 'null';
1107 5299 100       13398 return $x eq $y if $types[0] eq 'string';
1108 3843 100       16089 return $x == $y if grep $types[0] eq $_, qw(boolean number integer);
1109              
1110 548         998 my $path = $state->{path};
1111 548 100       1515 if ($types[0] eq 'object') {
1112 217 100       666 return 0 if keys %$x != keys %$y;
1113 201 100       1055 return 0 if not is_equal([ sort keys %$x ], [ sort keys %$y ]);
1114 186         691 foreach my $property (sort keys %$x) {
1115 218         517 $state->{path} = jsonp($path, $property);
1116 218 100       625 return 0 if not is_equal($x->{$property}, $y->{$property}, $state);
1117             }
1118 100         585 return 1;
1119             }
1120              
1121 331 50       769 if ($types[0] eq 'array') {
1122 331 100       737 return 0 if @$x != @$y;
1123 323         788 foreach my $idx (0..$x->$#*) {
1124 361         965 $state->{path} = $path.'/'.$idx;
1125 361 100       939 return 0 if not is_equal($x->[$idx], $y->[$idx], $state);
1126             }
1127 226         926 return 1;
1128             }
1129              
1130 0         0 return 0; # should never get here
1131             }
1132              
1133             # checks array elements for uniqueness. short-circuits on first pair of matching elements
1134             # if second arrayref is provided, it is populated with the indices of identical items
1135 2729     2729 0 4168 sub is_elements_unique ($array, $equal_indices = undef) {
  2729         3992  
  2729         4078  
  2729         3746  
1136 2729         7523 foreach my $idx0 (0..$array->$#*-1) {
1137 1527         10024 foreach my $idx1 ($idx0+1..$array->$#*) {
1138 3805 100       59317 if (is_equal($array->[$idx0], $array->[$idx1])) {
1139 201 50       1948 push @$equal_indices, $idx0, $idx1 if defined $equal_indices;
1140 201         646 return 0;
1141             }
1142             }
1143             }
1144 2528         6820 return 1;
1145             }
1146              
1147             # shorthand for creating and appending json pointers
1148             sub jsonp {
1149 43848 100   43848 0 406721 return join('/', shift, map s/~/~0/gr =~ s!/!~1!gr, map +(is_plain_arrayref($_) ? @$_ : $_), grep defined, @_);
1150             }
1151              
1152             # shorthand for finding the canonical uri of the present schema location
1153 30396     30396 0 45628 sub canonical_uri ($state, @extra_path) {
  30396         44284  
  30396         54174  
  30396         41066  
1154 30396 100 100     86892 splice(@extra_path, -1, 1, $extra_path[-1]->@*) if @extra_path and is_plain_arrayref($extra_path[-1]);
1155 30396         94088 my $uri = $state->{initial_schema_uri}->clone;
1156 30396   100     991885 $uri->fragment(($uri->fragment//'').jsonp($state->{schema_path}, @extra_path));
1157 30396 100       206935 $uri->fragment(undef) if not length($uri->fragment);
1158 30396         208748 $uri;
1159             }
1160              
1161             # shorthand for creating error objects
1162 9416     9416 0 32424 sub E ($state, $error_string, @args) {
  9416         14071  
  9416         13682  
  9416         14730  
  9416         11848  
1163             # sometimes the keyword shouldn't be at the very end of the schema path
1164 9416         30242 my $uri = canonical_uri($state, $state->{keyword}, $state->{_schema_path_suffix});
1165              
1166             my $keyword_location = $state->{traversed_schema_path}
1167 9416         28480 .jsonp($state->{schema_path}, $state->{keyword}, delete $state->{_schema_path_suffix});
1168              
1169 9416 100 100     33741 undef $uri if $uri eq '' and $keyword_location eq ''
      100        
      100        
      100        
1170             or ($uri->fragment//'') eq $keyword_location and $uri->clone->fragment(undef) eq '';
1171              
1172             push $state->{errors}->@*, {
1173             instanceLocation => $state->{data_path},
1174 9416 100       3498612 keywordLocation => $keyword_location,
    100          
1175             defined $uri ? ( absoluteKeywordLocation => $uri->to_string) : (),
1176             error => @args ? sprintf($error_string, @args) : $error_string,
1177             };
1178              
1179 9416         260232 return 0;
1180             }
1181              
1182             # creates an error object, but also aborts evaluation immediately
1183             # only this error is returned, because other errors on the stack might not actually be "real"
1184             # errors (consider if we were in the middle of evaluating a "not" or "if")
1185 1607     1607 0 293072 sub abort ($state, $error_string, @args) {
  1607         2524  
  1607         2563  
  1607         2513  
  1607         2191  
1186 1607         4301 E($state, $error_string, @args);
1187 1607         20844 die pop $state->{errors}->@*;
1188             }
1189              
1190             # one common usecase of abort()
1191 26793     26793 0 36812 sub assert_keyword_type ($state, $schema, $type) {
  26793         36383  
  26793         39327  
  26793         39531  
  26793         35243  
1192 26793         52526 my $value = $schema->{$state->{keyword}};
1193             $value = is_plain_hashref($value) ? $value->{$state->{_schema_path_suffix}}
1194             : is_plain_arrayref($value) ? $value->[$state->{_schema_path_suffix}]
1195             : die 'unknown type'
1196 26793 0       56458 if exists $state->{_schema_path_suffix};
    50          
    100          
1197 26793 100       53482 return 1 if is_type($type, $value);
1198 8 100       67 abort($state, '%s value is not a%s %s', $state->{keyword}, ($type =~ /^[aeiou]/ ? 'n' : ''), $type);
1199             }
1200              
1201 2143     2143 0 3272 sub assert_pattern ($state, $pattern) {
  2143         3049  
  2143         3074  
  2143         2891  
1202             try {
1203 0     0   0 local $SIG{__WARN__} = sub { die @_ };
1204             qr/$pattern/;
1205             }
1206 2143         4420 catch ($e) { abort($state, $e); };
1207 2140         17434 return 1;
1208             }
1209              
1210 2279     2279 0 3178 sub assert_uri_reference ($state, $schema) {
  2279         3359  
  2279         3023  
  2279         2969  
1211 2279         4406 my $ref = $schema->{$state->{keyword}};
1212              
1213             abort($state, '%s value is not a valid URI reference', $state->{keyword})
1214             # see also uri-reference format sub
1215 2279 50 33     7659 if fc(Mojo::URL->new($ref)->to_unsafe_string) ne fc($ref)
      100        
      100        
      66        
      33        
1216             or $ref =~ /[^[:ascii:]]/
1217             or $ref =~ /#/
1218             and $ref !~ m{#$} # empty fragment
1219             and $ref !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
1220             and $ref !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
1221              
1222 2279         775409 return 1;
1223             }
1224              
1225 5216     5216 0 6937 sub assert_uri ($state, $schema, $override = undef) {
  5216         7259  
  5216         6883  
  5216         7654  
  5216         6805  
1226 5216   66     16596 my $string = $override // $schema->{$state->{keyword}};
1227 5216         16893 my $uri = Mojo::URL->new($string);
1228              
1229 5216 0 33     420345 abort($state, '"%s" is not a valid URI', $string)
      33        
      66        
      33        
      33        
      33        
1230             # see also uri format sub
1231             if fc($uri->to_unsafe_string) ne fc($string)
1232             or $string =~ /[^[:ascii:]]/
1233             or not $uri->is_abs
1234             or $string =~ /#/
1235             and $string !~ m{#$} # empty fragment
1236             and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
1237             and $string !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
1238              
1239 5216         1005176 return 1;
1240             }
1241              
1242 2780     2780 0 4180 sub assert_non_negative_integer ($schema, $state) {
  2780         3922  
  2780         3909  
  2780         3711  
1243 2780         6555 assert_keyword_type($state, $schema, 'integer');
1244             abort($state, '%s value is not a non-negative integer', $state->{keyword})
1245 2780 50       9087 if $schema->{$state->{keyword}} < 0;
1246 2780         24032 return 1;
1247             }
1248              
1249 1886     1886 0 2800 sub assert_array_schemas ($schema, $state) {
  1886         2770  
  1886         2638  
  1886         2581  
1250 1886         4584 assert_keyword_type($state, $schema, 'array');
1251 1886 50       5511 abort($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
1252 1886         3192 return 1;
1253             }
1254              
1255 971     971 0 1426 sub sprintf_num ($value) {
  971         1564  
  971         1372  
1256             # use original value as stored in the NV, without losing precision
1257 971 100       4593 ref($value) =~ /^Math::Big(?:Int|Float)$/ ? $value->bstr : sprintf('%s', $value);
1258             }
1259              
1260             1;
1261              
1262             __END__
1263              
1264             =pod
1265              
1266             =encoding UTF-8
1267              
1268             =for stopwords schema subschema metaschema validator evaluator
1269              
1270             =head1 NAME
1271              
1272             JSON::Schema::Tiny - Validate data against a schema, minimally
1273              
1274             =head1 VERSION
1275              
1276             version 0.020
1277              
1278             =head1 SYNOPSIS
1279              
1280             my $data = { hello => 1 };
1281             my $schema = {
1282             type => "object",
1283             properties => { hello => { type => "integer" } },
1284             };
1285              
1286             # functional interface:
1287             use JSON::Schema::Tiny qw(evaluate);
1288             my $result = evaluate($data, $schema); # { valid => true }
1289              
1290             # object-oriented interface:
1291             use JSON::Schema::Tiny;
1292             my $js = JSON::Schema::Tiny->new;
1293             my $result = $js->evaluate($data, $schema); # { valid => true }
1294              
1295             =head1 DESCRIPTION
1296              
1297             This module aims to be a slimmed-down L<JSON Schema|https://json-schema.org/> evaluator and
1298             validator, supporting the most popular keywords.
1299             (See L</UNSUPPORTED JSON-SCHEMA FEATURES> below for exclusions.)
1300              
1301             =head1 FUNCTIONS
1302              
1303             =for Pod::Coverage is_type get_type is_equal is_elements_unique jsonp canonical_uri E abort
1304             assert_keyword_type assert_pattern assert_uri assert_non_negative_integer assert_array_schemas
1305             new assert_uri_reference sprintf_num
1306              
1307             =head2 evaluate
1308              
1309             my $result = evaluate($data, $schema);
1310              
1311             Evaluates the provided instance data against the known schema document.
1312              
1313             The data is in the form of an unblessed nested Perl data structure representing any type that JSON
1314             allows: null, boolean, string, number, object, array. (See L</TYPES> below.)
1315              
1316             The schema must represent a valid JSON Schema in the form of a Perl data structure, such as what is
1317             returned from a JSON decode operation.
1318              
1319             With default configuration settings, the return value is a hashref indicating the validation success
1320             or failure, plus (when validation failed), an arrayref of error strings in standard JSON Schema
1321             format. For example:
1322              
1323             running:
1324              
1325             $result = evaluate(1, { type => 'number' });
1326              
1327             C<$result> is:
1328              
1329             { valid => true }
1330              
1331             running:
1332              
1333             $result = evaluate(1, { type => 'number', multipleOf => 2 });
1334              
1335             C<$result> is:
1336              
1337             {
1338             valid => false,
1339             errors => [
1340             {
1341             instanceLocation => '',
1342             keywordLocation => '/multipleOf',
1343             error => 'value is not a multiple of 2',
1344             },
1345             ],
1346             }
1347              
1348             When L</C<$BOOLEAN_RESULT>> is true, the return value is a boolean (indicating evaluation success or
1349             failure).
1350              
1351             =head1 OPTIONS
1352              
1353             All options are available as package-scoped global variables. Use L<local|perlfunc/local> to
1354             configure them for a local scope. They may also be set via the constructor, as lower-cased values in
1355             a hash, e.g.: C<< JSON::Schema::Tiny->new(boolean_result => 1, max_traversal_depth => 10); >>
1356              
1357             =head2 C<$BOOLEAN_RESULT>
1358              
1359             When true, L</evaluate> will return a true or false result only, with no error strings. This enables
1360             short-circuit mode internally as this cannot effect results except get there faster. Defaults to false.
1361              
1362             =head2 C<$SHORT_CIRCUIT>
1363              
1364             When true, L</evaluate> will return from evaluating each subschema as soon as a true or false result
1365             can be determined. When C<$BOOLEAN_RESULT> is false, an incomplete list of errors will be returned.
1366             Defaults to false.
1367              
1368             =head2 C<$MAX_TRAVERSAL_DEPTH>
1369              
1370             The maximum number of levels deep a schema traversal may go, before evaluation is halted. This is to
1371             protect against accidental infinite recursion, such as from two subschemas that each reference each
1372             other, or badly-written schemas that could be optimized. Defaults to 50.
1373              
1374             =head2 C<$SCALARREF_BOOLEANS>
1375              
1376             When true, any type that is expected to be a boolean B<in the instance data> may also be expressed as
1377             the scalar references C<\0> or C<\1> (which are serialized as booleans by JSON backends).
1378             Defaults to false.
1379              
1380             =head2 C<$SPECIFICATION_VERSION>
1381              
1382             When set, the version of the draft specification is locked to one particular value, and use of
1383             keywords inconsistent with that specification version will result in an error. Will be set
1384             internally automatically with the use of the C<$schema> keyword. When not set, all keywords will be
1385             honoured (when otherwise supported).
1386              
1387             Supported values for this option, and the corresponding values for the C<$schema> keyword, are:
1388              
1389             =over 4
1390              
1391             =item *
1392              
1393             L<C<draft2020-12> or C<2020-12>|https://json-schema.org/specification-links.html#2020-12>, corresponding to metaschema C<https://json-schema.org/draft/2020-12/schema>
1394              
1395             =item *
1396              
1397             L<C<draft2019-09> or C<2019-09>|https://json-schema.org/specification-links.html#2019-09-formerly-known-as-draft-8>, corresponding to metaschema C<https://json-schema.org/draft/2019-09/schema>
1398              
1399             =item *
1400              
1401             L<C<draft7> or C<7>|https://json-schema.org/specification-links.html#draft-7>, corresponding to metaschema C<http://json-schema.org/draft-07/schema#>
1402              
1403             =back
1404              
1405             Defaults to undef.
1406              
1407             =head1 UNSUPPORTED JSON-SCHEMA FEATURES
1408              
1409             Unlike L<JSON::Schema::Modern>, this is not a complete implementation of the JSON Schema
1410             specification. Some features and keywords are left unsupported in order to keep the code small and
1411             the execution fast. These features are not available:
1412              
1413             =over 4
1414              
1415             =item *
1416              
1417             any output format other than C<flag> (when C<$BOOLEAN_RESULT> is true) or C<basic> (when it is false)
1418              
1419             =item *
1420              
1421             L<annotations|https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.7.7> in successful evaluation results
1422              
1423             =item *
1424              
1425             use of C<$ref> other than to locations in the local schema in json-pointer format (e.g. C<#/path/to/property>). This means that references to external documents, either those available locally or on the network, are not permitted.
1426              
1427             =back
1428              
1429             In addition, these keywords are implemented only partially or not at all (their presence in a schema
1430             will be ignored or possibly result in an error):
1431              
1432             =over 4
1433              
1434             =item *
1435              
1436             C<$schema> - only accepted if set to one of the specification metaschema URIs (see L<$SPECIFICATION_VERSION> for supported values)
1437              
1438             =item *
1439              
1440             C<$id>
1441              
1442             =item *
1443              
1444             C<$anchor>
1445              
1446             =item *
1447              
1448             C<$recursiveAnchor> and C<$recursiveRef> (draft2019-09), and C<$dynamicAnchor> and C<$dynamicRef> (draft2020-12 and thereafter)
1449              
1450             =item *
1451              
1452             C<$vocabulary>
1453              
1454             =item *
1455              
1456             C<unevaluatedItems> and C<unevaluatedProperties> (which require annotation support)
1457              
1458             =item *
1459              
1460             C<format> (does not cause an error when used)
1461              
1462             =back
1463              
1464             For a more full-featured implementation of the JSON Schema specification, see
1465             L<JSON::Schema::Modern>.
1466              
1467             =head1 LIMITATIONS
1468              
1469             =head2 Types
1470              
1471             Perl is a more loosely-typed language than JSON. This module delves into a value's internal
1472             representation in an attempt to derive the true "intended" type of the value. However, if a value is
1473             used in another context (for example, a numeric value is concatenated into a string, or a numeric
1474             string is used in an arithmetic operation), additional flags can be added onto the variable causing
1475             it to resemble the other type. This should not be an issue if data validation is occurring
1476             immediately after decoding a JSON (or YAML) payload.
1477              
1478             For more information, see L<Cpanel::JSON::XS/MAPPING>.
1479              
1480             =head1 SECURITY CONSIDERATIONS
1481              
1482             The C<pattern> and C<patternProperties> keywords evaluate regular expressions from the schema.
1483             No effort is taken (at this time) to sanitize the regular expressions for embedded code or
1484             potentially pathological constructs that may pose a security risk, either via denial of service
1485             or by allowing exposure to the internals of your application. B<DO NOT USE SCHEMAS FROM UNTRUSTED
1486             SOURCES.>
1487              
1488             =head1 SEE ALSO
1489              
1490             =over 4
1491              
1492             =item *
1493              
1494             L<JSON::Schema::Modern>: a more specification-compliant JSON Schema evaluator
1495              
1496             =item *
1497              
1498             L<Test::JSON::Schema::Acceptance>: contains the official JSON Schema test suite
1499              
1500             =item *
1501              
1502             L<https://json-schema.org>
1503              
1504             =item *
1505              
1506             L<Understanding JSON Schema|https://json-schema.org/understanding-json-schema>: tutorial-focused documentation
1507              
1508             =back
1509              
1510             =for stopwords OpenAPI
1511              
1512             =head1 SUPPORT
1513              
1514             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Tiny/issues>.
1515              
1516             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
1517              
1518             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
1519             server|https://open-api.slack.com>, which are also great resources for finding help.
1520              
1521             =head1 AUTHOR
1522              
1523             Karen Etheridge <ether@cpan.org>
1524              
1525             =head1 CONTRIBUTOR
1526              
1527             =for stopwords Matt S Trout
1528              
1529             Matt S Trout <mst@shadowcat.co.uk>
1530              
1531             =head1 COPYRIGHT AND LICENCE
1532              
1533             This software is copyright (c) 2021 by Karen Etheridge.
1534              
1535             This is free software; you can redistribute it and/or modify it under
1536             the same terms as the Perl 5 programming language system itself.
1537              
1538             =cut