File Coverage

blib/lib/JSON/Schema/Tiny.pm
Criterion Covered Total %
statement 859 865 99.3
branch 494 544 90.8
condition 256 314 81.5
subroutine 98 99 98.9
pod 1 17 5.8
total 1708 1839 92.8


line stmt bran cond sub pod time code
1 16     16   5008235 use strictures 2;
  16         288  
  16         710  
2             package JSON::Schema::Tiny; # git description: v0.020-6-g695f073
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.021';
8              
9 16     16   4039 use 5.020; # for unicode_strings, signatures, postderef features
  16         56  
10 16     16   102 use experimental 0.026 qw(signatures postderef args_array_with_signatures);
  16         297  
  16         125  
11 16     16   3876 no if "$]" >= 5.031009, feature => 'indirect';
  16         40  
  16         175  
12 16     16   836 no if "$]" >= 5.033001, feature => 'multidimensional';
  16         34  
  16         96  
13 16     16   741 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  16         39  
  16         88  
14 16     16   657 use B;
  16         34  
  16         1037  
15 16     16   5029 use Ref::Util 0.100 qw(is_plain_arrayref is_plain_hashref is_ref is_plain_arrayref);
  16         16385  
  16         1176  
16 16     16   8170 use Mojo::URL;
  16         2802197  
  16         117  
17 16     16   7607 use Mojo::JSON::Pointer;
  16         9634  
  16         102  
18 16     16   670 use Carp qw(croak carp);
  16         33  
  16         966  
19 16     16   9178 use Storable 'dclone';
  16         47398  
  16         1198  
20 16     16   4795 use JSON::MaybeXS 1.004001 'is_bool';
  16         53434  
  16         893  
21 16     16   4744 use Feature::Compat::Try;
  16         2983  
  16         155  
22 16     16   36736 use JSON::PP ();
  16         217755  
  16         564  
23 16     16   132 use List::Util 1.33 qw(any none);
  16         364  
  16         1180  
24 16     16   107 use Scalar::Util 'blessed';
  16         35  
  16         896  
25 16     16   98 use if "$]" >= 5.022, POSIX => 'isinf';
  16         32  
  16         168  
26 16     16   44562 use Math::BigFloat;
  16         861197  
  16         87  
27 16     16   374552 use namespace::clean;
  16         139796  
  16         138  
28 16     16   6062 use Exporter 5.57 'import';
  16         284  
  16         218271  
29              
30             our @EXPORT_OK = qw(evaluate);
31              
32             our $BOOLEAN_RESULT = 0;
33             our $SHORT_CIRCUIT = 0;
34             our $MAX_TRAVERSAL_DEPTH = 50;
35             our $MOJO_BOOLEANS; # deprecated; renamed to $SCALARREF_BOOLEANS
36             our $SCALARREF_BOOLEANS;
37             our $SPECIFICATION_VERSION;
38              
39             my %version_uris = (
40             'https://json-schema.org/draft/2020-12/schema' => 'draft2020-12',
41             'https://json-schema.org/draft/2019-09/schema' => 'draft2019-09',
42             'http://json-schema.org/draft-07/schema#' => 'draft7',
43             );
44              
45 18     18 0 102004 sub new ($class, %args) {
  18         41  
  18         51  
  18         27  
46 18         65 bless(\%args, $class);
47             }
48              
49             sub evaluate {
50 12233 50   12233 1 19369555 croak 'evaluate called in void context' if not defined wantarray;
51              
52 12233   66     44020 $SCALARREF_BOOLEANS = $SCALARREF_BOOLEANS // $MOJO_BOOLEANS;
53             local $BOOLEAN_RESULT = $_[0]->{boolean_result} // $BOOLEAN_RESULT,
54             local $SHORT_CIRCUIT = $_[0]->{short_circuit} // $SHORT_CIRCUIT,
55             local $MAX_TRAVERSAL_DEPTH = $_[0]->{max_traversal_depth} // $MAX_TRAVERSAL_DEPTH,
56             local $SCALARREF_BOOLEANS = $_[0]->{scalarref_booleans} // $SCALARREF_BOOLEANS // $_[0]->{mojo_booleans},
57 12233 100 33     197616 local $SPECIFICATION_VERSION = $_[0]->{specification_version} // $SPECIFICATION_VERSION,
      66        
      66        
      33        
      33        
      66        
      100        
58             shift
59             if blessed($_[0]) and blessed($_[0])->isa(__PACKAGE__);
60              
61 12233 100       36142 if (defined $SPECIFICATION_VERSION) {
62             $SPECIFICATION_VERSION = 'draft'.$SPECIFICATION_VERSION
63 12105 100 100 9   49233 if $SPECIFICATION_VERSION !~ /^draft/ and any { 'draft'.$SPECIFICATION_VERSION eq $_ } values %version_uris;
  9         32  
64              
65 12105 100   30199   67498 croak '$SPECIFICATION_VERSION value is invalid' if none { $SPECIFICATION_VERSION eq $_ } values %version_uris;
  30199         58751  
66             }
67              
68 12232 50       45720 croak 'insufficient arguments' if @_ < 2;
69 12232         26283 my ($data, $schema) = @_;
70              
71 12232   100     41131 my $state = {
72             depth => 0,
73             data_path => '',
74             traversed_schema_path => '', # the accumulated traversal path up to the last $ref traversal
75             initial_schema_uri => Mojo::URL->new, # the canonical URI as of the start or the last traversed $ref
76             schema_path => '', # the rest of the path, since the start or the last traversed $ref
77             errors => [],
78             seen => {},
79             short_circuit => $BOOLEAN_RESULT || $SHORT_CIRCUIT,
80             root_schema => $schema, # so we can do $refs within the same document
81             spec_version => $SPECIFICATION_VERSION,
82             };
83              
84 12232         192159 my $valid;
85             try {
86             $valid = _eval_subschema($data, $schema, $state)
87             }
88 12232         25444 catch ($e) {
89             if (is_plain_hashref($e)) {
90             push $state->{errors}->@*, $e;
91             }
92             else {
93             E($state, 'EXCEPTION: '.$e);
94             }
95              
96             $valid = 0;
97             }
98              
99 12232 50 66     41217 warn 'result is false but there are no errors' if not $valid and not $state->{errors}->@*;
100              
101             return $BOOLEAN_RESULT ? $valid : +{
102             valid => $valid ? JSON::PP::true : JSON::PP::false,
103 12232 100       50498 $valid ? () : (errors => $state->{errors}),
    100          
    100          
104             };
105             }
106              
107             ######## NO PUBLIC INTERFACES FOLLOW THIS POINT ########
108              
109             # current spec version => { keyword => undef, or arrayref of alternatives }
110             my %removed_keywords = (
111             'draft7' => {
112             id => [ '$id' ],
113             },
114             'draft2019-09' => {
115             id => [ '$id' ],
116             definitions => [ '$defs' ],
117             dependencies => [ qw(dependentSchemas dependentRequired) ],
118             },
119             'draft2020-12' => {
120             id => [ '$id' ],
121             definitions => [ '$defs' ],
122             dependencies => [ qw(dependentSchemas dependentRequired) ],
123             '$recursiveAnchor' => [ '$dynamicAnchor' ],
124             '$recursiveRef' => [ '$dynamicRef' ],
125             additionalItems => [ 'items' ],
126             },
127             );
128              
129 20732     20732   31065 sub _eval_subschema ($data, $schema, $state) {
  20732         31605  
  20732         28860  
  20732         27625  
  20732         28044  
130 20732 50       41888 croak '_eval_subschema called in void context' if not defined wantarray;
131              
132             # do not propagate upwards changes to depth, traversed paths,
133             # but additions to errors are by reference and will be retained
134 20732         117677 $state = { %$state };
135 20732         141861 delete $state->@{'keyword', grep /^_/, keys %$state};
136              
137             abort($state, 'EXCEPTION: maximum evaluation depth exceeded')
138 20732 100       66190 if $state->{depth}++ > $MAX_TRAVERSAL_DEPTH;
139              
140             # find all schema locations in effect at this data path + canonical_uri combination
141             # if any of them are absolute prefix of this schema location, we are in a loop.
142 20729         42764 my $canonical_uri = canonical_uri($state);
143 20729         46618 my $schema_location = $state->{traversed_schema_path}.$state->{schema_path};
144             abort($state, 'EXCEPTION: infinite loop detected (same location evaluated twice)')
145             if grep substr($schema_location, 0, length) eq $_,
146 20729 100       85271 keys $state->{seen}{$state->{data_path}}{$canonical_uri}->%*;
147 20727         3643679 $state->{seen}{$state->{data_path}}{$canonical_uri}{$schema_location}++;
148              
149 20727         2574288 my $schema_type = get_type($schema);
150 20727 100 66     56829 return $schema || E($state, 'subschema is false') if $schema_type eq 'boolean';
151 19939 100       41660 abort($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object';
152              
153 19920 100       48931 return 1 if not keys %$schema;
154              
155 19669         30082 my $valid = 1;
156 19669   100     45432 my $spec_version = $state->{spec_version}//'';
157              
158 19669 100 100     645234 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        
159             # CORE KEYWORDS
160             qw($id $schema),
161             !$spec_version || $spec_version ne 'draft7' ? '$anchor' : (),
162             !$spec_version || $spec_version eq 'draft2019-09' ? '$recursiveAnchor' : (),
163             !$spec_version || $spec_version eq 'draft2020-12' ? '$dynamicAnchor' : (),
164             '$ref',
165             !$spec_version || $spec_version eq 'draft2019-09' ? '$recursiveRef' : (),
166             !$spec_version || $spec_version eq 'draft2020-12' ? '$dynamicRef' : (),
167             !$spec_version || $spec_version ne 'draft7' ? qw($vocabulary $comment) : (),
168             !$spec_version || $spec_version eq 'draft7' ? 'definitions' : (),
169             !$spec_version || $spec_version ne 'draft7' ? '$defs' : (),
170             # APPLICATOR KEYWORDS
171             qw(allOf anyOf oneOf not if),
172             !$spec_version || $spec_version ne 'draft7' ? 'dependentSchemas' : (),
173             !$spec_version || $spec_version eq 'draft7' ? 'dependencies' : (),
174             !$spec_version || $spec_version !~ qr/^draft(7|2019-09)$/ ? 'prefixItems' : (),
175             'items',
176             !$spec_version || $spec_version =~ qr/^draft(?:7|2019-09)$/ ? 'additionalItems' : (),
177             qw(contains properties patternProperties additionalProperties propertyNames),
178             # UNEVALUATED KEYWORDS
179             !$spec_version || $spec_version ne 'draft7' ? qw(unevaluatedItems unevaluatedProperties) : (),
180             # VALIDATOR KEYWORDS
181             qw(type enum const
182             multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
183             maxLength minLength pattern
184             maxItems minItems uniqueItems),
185             !$spec_version || $spec_version ne 'draft7' ? qw(maxContains minContains) : (),
186             qw(maxProperties minProperties required),
187             !$spec_version || $spec_version ne 'draft7' ? 'dependentRequired' : (),
188             ) {
189 686136 100       1219957 next if not exists $schema->{$keyword};
190              
191             # keywords adjacent to $ref (except for definitions) are not evaluated before draft2019-09
192             next if $keyword ne '$ref' and $keyword ne 'definitions'
193 32648 100 100     145086 and exists $schema->{'$ref'} and $spec_version eq 'draft7';
      100        
      100        
194              
195 32633         63350 $state->{keyword} = $keyword;
196 32633         52734 my $error_count = $state->{errors}->@*;
197              
198 32633         197702 my $sub = __PACKAGE__->can('_eval_keyword_'.($keyword =~ s/^\$//r));
199 32633 100       88637 if (not $sub->($data, $schema, $state)) {
200             warn 'result is false but there are no errors (keyword: '.$keyword.')'
201 7145 50       18771 if $error_count == $state->{errors}->@*;
202 7145         11944 $valid = 0;
203             }
204              
205 30117 100 100     144812 last if not $valid and $state->{short_circuit};
206             }
207              
208             # check for previously-supported but now removed keywords
209 17153         96837 foreach my $keyword (sort keys $removed_keywords{$spec_version}->%*) {
210 59868 100       126875 next if not exists $schema->{$keyword};
211 214         683 my $message ='no-longer-supported "'.$keyword.'" keyword present (at location "'
212             .canonical_uri($state).'")';
213 214 50       34095 if (my $alternates = $removed_keywords{$spec_version}->{$keyword}) {
214 214         1098 my @list = map '"'.$_.'"', @$alternates;
215 214 50       596 @list = ((map $_.',', @list[0..$#list-1]), $list[-1]) if @list > 2;
216 214 100       655 splice(@list, -1, 0, 'or') if @list > 1;
217 214         728 $message .= ': this should be rewritten as '.join(' ', @list);
218             }
219 214         19378 carp $message;
220             }
221              
222 17153         130860 return $valid;
223             }
224              
225             # KEYWORD IMPLEMENTATIONS
226              
227 5212     5212   6972 sub _eval_keyword_schema ($data, $schema, $state) {
  5212         7883  
  5212         7290  
  5212         6848  
  5212         6324  
228 5212         13419 assert_keyword_type($state, $schema, 'string');
229 5212         15142 assert_uri($state, $schema);
230              
231             return abort($state, '$schema can only appear at the schema resource root')
232 5212 100       14321 if length($state->{schema_path});
233              
234 5211         12495 my $spec_version = $version_uris{$schema->{'$schema'}};
235 5211 100       10138 abort($state, 'custom $schema URIs are not supported (must be one of: %s',
236             join(', ', map '"'.$_.'"', keys %version_uris))
237             if not $spec_version;
238              
239 5182 100 100     17912 abort($state, '"$schema" indicates a different version than that requested by $JSON::Schema::Tiny::SPECIFICATION_VERSION')
240             if defined $SPECIFICATION_VERSION and $SPECIFICATION_VERSION ne $spec_version;
241              
242             # we special-case this because the check in _eval for older drafts + $ref has already happened
243             abort($state, '$schema and $ref cannot be used together in older drafts')
244 5181 100 100     11777 if exists $schema->{'$ref'} and $spec_version eq 'draft7';
245              
246 5180         14321 $state->{spec_version} = $spec_version;
247             }
248              
249 1633     1633   2433 sub _eval_keyword_ref ($data, $schema, $state) {
  1633         2725  
  1633         2341  
  1633         2293  
  1633         2425  
250 1633         4240 assert_keyword_type($state, $schema, 'string');
251 1633         5019 assert_uri_reference($state, $schema);
252              
253 1633         7129 my $uri = Mojo::URL->new($schema->{$state->{keyword}})->to_abs($state->{initial_schema_uri});
254             abort($state, '%ss to anchors are not supported', $state->{keyword})
255 1633 100 100     679543 if ($uri->fragment//'') !~ m{^(/(?:[^~]|~[01])*|)$};
256              
257             # the base of the $ref uri must be the same as the base of the root schema
258             # unfortunately this means that many uses of $ref won't work, because we don't
259             # track the locations of $ids in this or other documents.
260             abort($state, 'only same-document, same-base JSON pointers are supported in %s', $state->{keyword})
261 1539 100 100     19777 if $uri->clone->fragment(undef) ne Mojo::URL->new($state->{root_schema}{'$id'}//'');
262              
263 1060   100     576750 my $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment//'');
264 1060 100       49693 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
265              
266             return _eval_subschema($data, $subschema,
267             +{ %$state,
268             traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/'.$state->{keyword},
269 1055         11332 initial_schema_uri => $uri,
270             schema_path => '',
271             });
272             }
273              
274 52     52   82 sub _eval_keyword_recursiveRef ($data, $schema, $state) {
  52         82  
  52         75  
  52         65  
  52         83  
275 52         133 assert_keyword_type($state, $schema, 'string');
276 52         160 assert_uri_reference($state, $schema);
277              
278 52         203 my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
279 52 50 50     23257 abort($state, '$recursiveRefs to anchors are not supported')
280             if ($uri->fragment//'') !~ m{^(/(?:[^~]|~[01])*|)$};
281              
282             # the base of the $recursiveRef uri must be the same as the base of the root schema.
283             # unfortunately this means that nearly all usecases of $recursiveRef won't work, because we don't
284             # track the locations of $ids in this or other documents.
285             abort($state, 'only same-document, same-base JSON pointers are supported in $recursiveRef')
286 52 100 100     540 if $uri->clone->fragment(undef) ne Mojo::URL->new($state->{root_schema}{'$id'}//'');
287              
288 8         4187 my $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment);
289 8 50       293 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
290              
291 8 0 33     33 if (is_type('boolean', $subschema->{'$recursiveAnchor'}) and $subschema->{'$recursiveAnchor'}) {
292             $uri = Mojo::URL->new($schema->{'$recursiveRef'})
293 0   0     0 ->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
294 0         0 $subschema = Mojo::JSON::Pointer->new($state->{root_schema})->get($uri->fragment);
295 0 0       0 abort($state, 'EXCEPTION: unable to find resource %s', $uri) if not defined $subschema;
296             }
297              
298             return _eval_subschema($data, $subschema,
299             +{ %$state,
300 8         134 traversed_schema_path => $state->{traversed_schema_path}.$state->{schema_path}.'/$recursiveRef',
301             initial_schema_uri => $uri,
302             schema_path => '',
303             });
304             }
305              
306 8     8   26 sub _eval_keyword_dynamicRef { goto \&_eval_keyword_ref }
307              
308 594     594   970 sub _eval_keyword_id ($data, $schema, $state) {
  594         972  
  594         889  
  594         880  
  594         816  
309 594         1542 assert_keyword_type($state, $schema, 'string');
310 594         1808 assert_uri_reference($state, $schema);
311              
312 594         2120 my $uri = Mojo::URL->new($schema->{'$id'});
313              
314 594 100 100     69923 if (($state->{spec_version}//'') eq 'draft7') {
315 121 100       297 if (length($uri->fragment)) {
316 3 50       24 abort($state, '$id cannot change the base uri at the same time as declaring an anchor')
317             if length($uri->clone->fragment(undef));
318              
319 3 100       485 abort($state, '$id value does not match required syntax')
320             if $uri->fragment !~ m/^[A-Za-z][A-Za-z0-9_:.-]*$/;
321              
322 2         30 return 1;
323             }
324             }
325             else {
326 473 100       1225 abort($state, '$id value "%s" cannot have a non-empty fragment', $uri) if length $uri->fragment;
327             }
328              
329 589         3478 $uri->fragment(undef);
330 589 100       4064 return E($state, '$id cannot be empty') if not length $uri;
331              
332 565 100       95349 $state->{initial_schema_uri} = $uri->is_abs ? $uri : $uri->to_abs($state->{initial_schema_uri});
333 565         63168 $state->{traversed_schema_path} = $state->{traversed_schema_path}.$state->{schema_path};
334 565         1122 $state->{schema_path} = '';
335              
336 565         1916 return 1;
337             }
338              
339 12     12   20 sub _eval_keyword_anchor ($data, $schema, $state) {
  12         19  
  12         21  
  12         15  
  12         16  
340 12         36 assert_keyword_type($state, $schema, 'string');
341              
342             return 1 if
343             (!$state->{spec_version} or $state->{spec_version} eq 'draft2019-09')
344             and ($schema->{'$anchor'}//'') =~ /^[A-Za-z][A-Za-z0-9_:.-]*$/
345             or
346             (!$state->{spec_version} or $state->{spec_version} eq 'draft2020-12')
347 12 50 66     164 and ($schema->{'$anchor'}//'') =~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
      50        
      66        
      33        
      50        
      33        
      66        
348              
349 0         0 abort($state, '$anchor value does not match required syntax');
350             }
351              
352 80     80   112 sub _eval_keyword_recursiveAnchor ($data, $schema, $state) {
  80         115  
  80         116  
  80         109  
  80         107  
353 80         195 assert_keyword_type($state, $schema, 'boolean');
354 80 100 100     1054 return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
355              
356             # this is required because the location is used as the base URI for future resolution
357             # of $recursiveRef, and the fragment would be disregarded in the base
358             abort($state, '"$recursiveAnchor" keyword used without "$id"')
359 39 50       327 if not exists $schema->{'$id'};
360              
361             # record the canonical location of the current position, to be used against future resolution
362             # of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
363 39         80 $state->{recursive_anchor_uri} = canonical_uri($state);
364              
365 39         107 return 1;
366             }
367              
368 14     14   25 sub _eval_keyword_dynamicAnchor ($data, $schema, $state) {
  14         20  
  14         22  
  14         21  
  14         19  
369 14 50       32 return if not assert_keyword_type($state, $schema, 'string');
370              
371             abort($state, '$dynamicAnchor value does not match required syntax')
372 14 50       73 if $schema->{'$dynamicAnchor'} !~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
373 14         37 return 1;
374             }
375              
376 4     4   9 sub _eval_keyword_vocabulary ($data, $schema, $state) {
  4         10  
  4         9  
  4         8  
  4         8  
377 4         13 assert_keyword_type($state, $schema, 'object');
378              
379 4         21 foreach my $property (sort keys $schema->{'$vocabulary'}->%*) {
380 4         51 assert_keyword_type({ %$state, _schema_path_suffix => $property }, $schema, 'boolean');
381 4         64 assert_uri($state, undef, $property);
382             }
383              
384             abort($state, '$vocabulary can only appear at the schema resource root')
385 4 50       17 if length($state->{schema_path});
386              
387             abort($state, '$vocabulary can only appear at the document root')
388 4 50       16 if length($state->{traversed_schema_path}.$state->{schema_path});
389              
390 4         12 return 1;
391             }
392              
393 180     180   276 sub _eval_keyword_comment ($data, $schema, $state) {
  180         269  
  180         254  
  180         229  
  180         240  
394 180         460 assert_keyword_type($state, $schema, 'string');
395 180         473 return 1;
396             }
397              
398 134     134   496 sub _eval_keyword_definitions { goto \&_eval_keyword_defs }
399              
400 625     625   1122 sub _eval_keyword_defs ($data, $schema, $state) {
  625         1101  
  625         943  
  625         931  
  625         885  
401 625         1717 assert_keyword_type($state, $schema, 'object');
402 623         1648 return 1;
403             }
404              
405 3579     3579   5271 sub _eval_keyword_type ($data, $schema, $state) {
  3579         5578  
  3579         4809  
  3579         4691  
  3579         5158  
406 3579 100       8169 if (is_plain_arrayref($schema->{type})) {
407 159 50       421 abort($state, 'type array is empty') if not $schema->{type}->@*;
408 159         383 foreach my $type ($schema->{type}->@*) {
409             abort($state, 'unrecognized type "%s"', $type//'<null>')
410 336 100 50 1359   1359 if not any { ($type//'') eq $_ } qw(null boolean object array string number integer);
  1359   100     3079  
411             }
412 153 50       393 abort($state, '"type" values are not unique') if not is_elements_unique($schema->{type});
413              
414 153         303 my $type = get_type($data);
415             return 1 if any {
416 284 50 100 284   1643 $type eq $_ or ($_ eq 'number' and $type eq 'integer')
      100        
      66        
      66        
417             or ($_ eq 'boolean' and $SCALARREF_BOOLEANS and $type eq 'reference to SCALAR')
418 153 100       863 } $schema->{type}->@*;
419 89         532 return E($state, 'got %s, not one of %s', $type, join(', ', $schema->{type}->@*));
420             }
421             else {
422 3420         8595 assert_keyword_type($state, $schema, 'string');
423             abort($state, 'unrecognized type "%s"', $schema->{type}//'<null>')
424 3414 100 50 15821   19702 if not any { ($schema->{type}//'') eq $_ } qw(null boolean object array string number integer);
  15821   50     33386  
425              
426 3412         12332 my $type = get_type($data);
427             return 1 if $type eq $schema->{type} or ($schema->{type} eq 'number' and $type eq 'integer')
428 3412 100 100     20742 or ($schema->{type} eq 'boolean' and $SCALARREF_BOOLEANS and $type eq 'reference to SCALAR');
      100        
      100        
      100        
      100        
429 943         2882 return E($state, 'got %s, not %s', $type, $schema->{type});
430             }
431             }
432              
433 385     385   581 sub _eval_keyword_enum ($data, $schema, $state) {
  385         652  
  385         548  
  385         528  
  385         530  
434 385         923 assert_keyword_type($state, $schema, 'array');
435              
436 385         690 my @s; my $idx = 0;
  385         575  
437 385 100   822   2467 return 1 if any { is_equal($data, $_, $s[$idx++] = {}) } $schema->{enum}->@*;
  822         9575  
438              
439             return E($state, 'value does not match'
440             .(!(grep $_->{path}, @s) ? ''
441 176 100       1366 : ' (differences start '.join(', ', map 'from item #'.$_.' at "'.$s[$_]->{path}.'"', 0..$#s).')'));
442             }
443              
444 1009     1009   1591 sub _eval_keyword_const ($data, $schema, $state) {
  1009         1708  
  1009         1430  
  1009         1345  
  1009         1359  
445 1009 100       2745 return 1 if is_equal($data, $schema->{const}, my $s = {});
446             return E($state, 'value does not match'
447 460 100       5159 .($s->{path} ? ' (differences start at "'.$s->{path}.'")' : ''));
448             }
449              
450 822     822   1270 sub _eval_keyword_multipleOf ($data, $schema, $state) {
  822         1353  
  822         1160  
  822         1198  
  822         1180  
451 822         2101 assert_keyword_type($state, $schema, 'number');
452 822 50       2135 abort($state, 'multipleOf value is not a positive number') if $schema->{multipleOf} <= 0;
453              
454 822 100       12276 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     2946 if (ref($data) =~ /^Math::Big(?:Int|Float)$/
      66        
      100        
458             or ref($schema->{multipleOf}) =~ /^Math::Big(?:Int|Float)$/
459             or get_type($data) eq 'number' or get_type($schema->{multipleOf}) eq 'number') {
460 49 100       250 $data = ref($data) =~ /^Math::Big(?:Int|Float)$/ ? $data->copy : Math::BigFloat->new($data);
461 49 100       2878 my $divisor = ref($schema->{multipleOf}) =~ /^Math::Big(?:Int|Float)$/ ? $schema->{multipleOf} : Math::BigFloat->new($schema->{multipleOf});
462 49         464 my ($quotient, $remainder) = $data->bdiv($divisor);
463 49 50       50192 return E($state, 'overflow while calculating quotient') if $quotient->is_inf;
464 49 100       485 return 1 if $remainder == 0;
465             }
466             else {
467 585         1444 my $quotient = $data / $schema->{multipleOf};
468 585 50       3298 return E($state, 'overflow while calculating quotient')
    50          
469             if "$]" >= 5.022 ? isinf($quotient) : $quotient =~ /^-?Inf$/i;
470 585 100       2087 return 1 if int($quotient) == $quotient;
471             }
472              
473 291         5239 return E($state, 'value is not a multiple of %s', sprintf_num($schema->{multipleOf}));
474             }
475              
476 579     579   919 sub _eval_keyword_maximum ($data, $schema, $state) {
  579         877  
  579         848  
  579         850  
  579         773  
477 579         1463 assert_keyword_type($state, $schema, 'number');
478 579 100       1263 return 1 if not is_type('number', $data);
479 385 100       1240 return 1 if $data <= $schema->{maximum};
480 169         4732 return E($state, 'value is larger than %s', sprintf_num($schema->{maximum}));
481             }
482              
483 477     477   767 sub _eval_keyword_exclusiveMaximum ($data, $schema, $state) {
  477         795  
  477         651  
  477         651  
  477         695  
484 477         1172 assert_keyword_type($state, $schema, 'number');
485 477 100       1029 return 1 if not is_type('number', $data);
486 289 100       940 return 1 if $data < $schema->{exclusiveMaximum};
487 151         4706 return E($state, 'value is equal to or larger than %s', sprintf_num($schema->{exclusiveMaximum}));
488             }
489              
490 707     707   1046 sub _eval_keyword_minimum ($data, $schema, $state) {
  707         1063  
  707         1022  
  707         955  
  707         942  
491 707         1794 assert_keyword_type($state, $schema, 'number');
492 707 100       1453 return 1 if not is_type('number', $data);
493 504 100       1556 return 1 if $data >= $schema->{minimum};
494 239         9465 return E($state, 'value is smaller than %s', sprintf_num($schema->{minimum}));
495             }
496              
497 417     417   588 sub _eval_keyword_exclusiveMinimum ($data, $schema, $state) {
  417         673  
  417         560  
  417         681  
  417         615  
498 417         1045 assert_keyword_type($state, $schema, 'number');
499 417 100       943 return 1 if not is_type('number', $data);
500 229 100       722 return 1 if $data > $schema->{exclusiveMinimum};
501 121         4026 return E($state, 'value is equal to or smaller than %s', sprintf_num($schema->{exclusiveMinimum}));
502             }
503              
504 553     553   829 sub _eval_keyword_maxLength ($data, $schema, $state) {
  553         969  
  553         795  
  553         806  
  553         775  
505 553         1495 assert_non_negative_integer($schema, $state);
506              
507 553 100       1053 return 1 if not is_type('string', $data);
508 344 100       1290 return 1 if length($data) <= $schema->{maxLength};
509 162         1809 return E($state, 'length is greater than %d', $schema->{maxLength});
510             }
511              
512 512     512   775 sub _eval_keyword_minLength ($data, $schema, $state) {
  512         855  
  512         690  
  512         713  
  512         718  
513 512         1367 assert_non_negative_integer($schema, $state);
514              
515 512 100       1001 return 1 if not is_type('string', $data);
516 302 100       1098 return 1 if length($data) >= $schema->{minLength};
517 142         1766 return E($state, 'length is less than %d', $schema->{minLength});
518             }
519              
520 895     895   1337 sub _eval_keyword_pattern ($data, $schema, $state) {
  895         1439  
  895         1301  
  895         1227  
  895         1223  
521 895         2307 assert_keyword_type($state, $schema, 'string');
522 895         2929 assert_pattern($state, $schema->{pattern});
523              
524 894 100       1857 return 1 if not is_type('string', $data);
525 667 100       5114 return 1 if $data =~ m/(?:$schema->{pattern})/;
526 313         861 return E($state, 'pattern does not match');
527             }
528              
529 425     425   684 sub _eval_keyword_maxItems ($data, $schema, $state) {
  425         681  
  425         601  
  425         557  
  425         557  
530 425         1087 assert_non_negative_integer($schema, $state);
531              
532 425 100       742 return 1 if not is_type('array', $data);
533 256 100       843 return 1 if @$data <= $schema->{maxItems};
534 122 100       1859 return E($state, 'more than %d item%s', $schema->{maxItems}, $schema->{maxItems} > 1 ? 's' : '');
535             }
536              
537 424     424   662 sub _eval_keyword_minItems ($data, $schema, $state) {
  424         673  
  424         592  
  424         567  
  424         591  
538 424         1074 assert_non_negative_integer($schema, $state);
539              
540 424 100       797 return 1 if not is_type('array', $data);
541 257 100       857 return 1 if @$data >= $schema->{minItems};
542 124 100       1316 return E($state, 'fewer than %d item%s', $schema->{minItems}, $schema->{minItems} > 1 ? 's' : '');
543             }
544              
545 769     769   1165 sub _eval_keyword_uniqueItems ($data, $schema, $state) {
  769         1172  
  769         1088  
  769         1046  
  769         1053  
546 769         1924 assert_keyword_type($state, $schema, 'boolean');
547 769 100       8216 return 1 if not is_type('array', $data);
548 608 100       2398 return 1 if not $schema->{uniqueItems};
549 443 100       4165 return 1 if is_elements_unique($data, my $equal_indices = []);
550 201         536 return E($state, 'items at indices %d and %d are not unique', @$equal_indices);
551             }
552              
553 84     84   125 sub _eval_keyword_maxContains ($data, $schema, $state) {
  84         128  
  84         121  
  84         111  
  84         113  
554 84         222 assert_non_negative_integer($schema, $state);
555 84 100       234 return 1 if not exists $state->{_num_contains};
556 76 50       155 return 1 if not is_type('array', $data);
557              
558             return E($state, 'contains too many matching items')
559 76 100       243 if $state->{_num_contains} > $schema->{maxContains};
560              
561 44         1114 return 1;
562             }
563              
564 102     102   155 sub _eval_keyword_minContains ($data, $schema, $state) {
  102         178  
  102         161  
  102         141  
  102         140  
565 102         260 assert_non_negative_integer($schema, $state);
566 102 100       265 return 1 if not exists $state->{_num_contains};
567 94 50       183 return 1 if not is_type('array', $data);
568              
569             return E($state, 'contains too few matching items')
570 94 100       276 if $state->{_num_contains} < $schema->{minContains};
571              
572 60         1093 return 1;
573             }
574              
575 340     340   515 sub _eval_keyword_maxProperties ($data, $schema, $state) {
  340         498  
  340         472  
  340         489  
  340         462  
576 340         902 assert_non_negative_integer($schema, $state);
577              
578 340 100       677 return 1 if not is_type('object', $data);
579 202 100       706 return 1 if keys %$data <= $schema->{maxProperties};
580             return E($state, 'more than %d propert%s', $schema->{maxProperties},
581 98 100       1774 $schema->{maxProperties} > 1 ? 'ies' : 'y');
582             }
583              
584 340     340   515 sub _eval_keyword_minProperties ($data, $schema, $state) {
  340         527  
  340         500  
  340         464  
  340         441  
585 340         904 assert_non_negative_integer($schema, $state);
586              
587 340 100       667 return 1 if not is_type('object', $data);
588 202 100       743 return 1 if keys %$data >= $schema->{minProperties};
589             return E($state, 'fewer than %d propert%s', $schema->{minProperties},
590 98 100       1273 $schema->{minProperties} > 1 ? 'ies' : 'y');
591             }
592              
593 1410     1410   2054 sub _eval_keyword_required ($data, $schema, $state) {
  1410         2248  
  1410         2100  
  1410         1912  
  1410         1963  
594 1410         3211 assert_keyword_type($state, $schema, 'array');
595             abort($state, '"required" element is not a string')
596 1410 50   1596   8341 if any { !is_type('string', $_) } $schema->{required}->@*;
  1596         3194  
597 1410 50       6148 abort($state, '"required" values are not unique') if not is_elements_unique($schema->{required});
598              
599 1410 100       2748 return 1 if not is_type('object', $data);
600              
601 1260         5080 my @missing = grep !exists $data->{$_}, $schema->{required}->@*;
602 1260 100       3738 return 1 if not @missing;
603 566 100       2565 return E($state, 'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
604             }
605              
606 271     271   403 sub _eval_keyword_dependentRequired ($data, $schema, $state) {
  271         411  
  271         396  
  271         389  
  271         379  
607 271         682 assert_keyword_type($state, $schema, 'object');
608              
609 271         1039 foreach my $property (sort keys $schema->{dependentRequired}->%*) {
610             E({ %$state, _schema_path_suffix => $property }, 'value is not an array'), next
611 287 50       645 if not is_type('array', $schema->{dependentRequired}{$property});
612              
613 287         951 foreach my $index (0..$schema->{dependentRequired}{$property}->$#*) {
614             abort({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
615 301 100       653 if not is_type('string', $schema->{dependentRequired}{$property}[$index]);
616             }
617              
618             abort({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
619 286 50       758 if not is_elements_unique($schema->{dependentRequired}{$property});
620             }
621              
622 270 100       567 return 1 if not is_type('object', $data);
623              
624 173         327 my $valid = 1;
625 173         486 foreach my $property (sort keys $schema->{dependentRequired}->%*) {
626 189 100       456 next if not exists $data->{$property};
627              
628 153 100       822 if (my @missing = grep !exists($data->{$_}), $schema->{dependentRequired}{$property}->@*) {
629 79 100       913 $valid = E({ %$state, _schema_path_suffix => $property },
630             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
631             }
632             }
633              
634 173 100       541 return 1 if $valid;
635 79         190 return E($state, 'not all dependencies are satisfied');
636             }
637              
638 549     549   838 sub _eval_keyword_allOf ($data, $schema, $state) {
  549         928  
  549         807  
  549         783  
  549         810  
639 549         1535 assert_array_schemas($schema, $state);
640              
641 549         903 my @invalid;
642 549         1784 foreach my $idx (0..$schema->{allOf}->$#*) {
643             next if _eval_subschema($data, $schema->{allOf}[$idx],
644 805 100       8009 +{ %$state, schema_path => $state->{schema_path}.'/allOf/'.$idx });
645              
646 187         812 push @invalid, $idx;
647 187 100       567 last if $state->{short_circuit};
648             }
649              
650 382 100       1764 return 1 if @invalid == 0;
651              
652 154         311 my $pl = @invalid > 1;
653 154 100       677 return E($state, 'subschema%s %s %s not valid', $pl?'s':'', join(', ', @invalid), $pl?'are':'is');
    100          
654             }
655              
656 433     433   664 sub _eval_keyword_anyOf ($data, $schema, $state) {
  433         720  
  433         615  
  433         640  
  433         689  
657 433         1293 assert_array_schemas($schema, $state);
658              
659 433         758 my $valid = 0;
660 433         650 my @errors;
661 433         1427 foreach my $idx (0..$schema->{anyOf}->$#*) {
662             next if not _eval_subschema($data, $schema->{anyOf}[$idx],
663 760 100       7820 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/anyOf/'.$idx });
664 239         1464 ++$valid;
665 239 100       753 last if $state->{short_circuit};
666             }
667              
668 294 100       1001 return 1 if $valid;
669 92         253 push $state->{errors}->@*, @errors;
670 92         222 return E($state, 'no subschemas are valid');
671             }
672              
673 509     509   748 sub _eval_keyword_oneOf ($data, $schema, $state) {
  509         738  
  509         785  
  509         755  
  509         737  
674 509         1437 assert_array_schemas($schema, $state);
675              
676 509         870 my (@valid, @errors);
677 509         1870 foreach my $idx (0..$schema->{oneOf}->$#*) {
678             next if not _eval_subschema($data, $schema->{oneOf}[$idx],
679 1061 100       11022 +{ %$state, errors => \@errors, schema_path => $state->{schema_path}.'/oneOf/'.$idx });
680 377         2203 push @valid, $idx;
681 377 100 100     1336 last if @valid > 1 and $state->{short_circuit};
682             }
683              
684 358 100       1379 return 1 if @valid == 1;
685              
686 201 100       509 if (not @valid) {
687 123         356 push $state->{errors}->@*, @errors;
688 123         276 return E($state, 'no subschemas are valid');
689             }
690             else {
691 78         366 return E($state, 'multiple subschemas are valid: '.join(', ', @valid));
692             }
693             }
694              
695 131     131   217 sub _eval_keyword_not ($data, $schema, $state) {
  131         240  
  131         190  
  131         200  
  131         166  
696             return 1 if not _eval_subschema($data, $schema->{not},
697 131 100       1331 +{ %$state, schema_path => $state->{schema_path}.'/not', short_circuit => 1, errors => [] });
698              
699 84         545 return E($state, 'subschema is valid');
700             }
701              
702 298     298   492 sub _eval_keyword_if ($data, $schema, $state) {
  298         521  
  298         482  
  298         460  
  298         431  
703 298 100 100     927 return 1 if not exists $schema->{then} and not exists $schema->{else};
704             my $keyword = _eval_subschema($data, $schema->{if},
705 270 100       2707 +{ %$state, schema_path => $state->{schema_path}.'/if', short_circuit => 1, errors => [] })
706             ? 'then' : 'else';
707              
708 270 100       1673 return 1 if not exists $schema->{$keyword};
709             return 1 if _eval_subschema($data, $schema->{$keyword},
710 224 100       1989 +{ %$state, schema_path => $state->{schema_path}.'/'.$keyword });
711 70         657 return E({ %$state, keyword => $keyword }, 'subschema is not valid');
712             }
713              
714 297     297   459 sub _eval_keyword_dependentSchemas ($data, $schema, $state) {
  297         450  
  297         403  
  297         423  
  297         410  
715 297         770 assert_keyword_type($state, $schema, 'object');
716              
717 297 100       664 return 1 if not is_type('object', $data);
718              
719 173         355 my $valid = 1;
720 173         644 foreach my $property (sort keys $schema->{dependentSchemas}->%*) {
721             next if not exists $data->{$property}
722             or _eval_subschema($data, $schema->{dependentSchemas}{$property},
723 199 100 100     965 +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependentSchemas', $property) });
724              
725 89         362 $valid = 0;
726 89 100       302 last if $state->{short_circuit};
727             }
728              
729 173 100       634 return E($state, 'not all dependencies are satisfied') if not $valid;
730 84         219 return 1;
731             }
732              
733 178     178   259 sub _eval_keyword_dependencies ($data, $schema, $state) {
  178         300  
  178         251  
  178         267  
  178         241  
734 178         465 assert_keyword_type($state, $schema, 'object');
735              
736 178 100       393 return 1 if not is_type('object', $data);
737              
738 111         201 my $valid = 1;
739 111         409 foreach my $property (sort keys $schema->{dependencies}->%*) {
740 158 100       400 if (is_type('array', $schema->{dependencies}{$property})) {
741             # as in dependentRequired
742              
743 52         158 foreach my $index (0..$schema->{dependencies}{$property}->$#*) {
744             $valid = E({ %$state, _schema_path_suffix => [ $property, $index ] }, 'element #%d is not a string', $index)
745 62 50       134 if not is_type('string', $schema->{dependencies}{$property}[$index]);
746             }
747              
748             abort({ %$state, _schema_path_suffix => $property }, 'elements are not unique')
749 52 50       135 if not is_elements_unique($schema->{dependencies}{$property});
750              
751 52 100       139 next if not exists $data->{$property};
752              
753 24 100       153 if (my @missing = grep !exists($data->{$_}), $schema->{dependencies}{$property}->@*) {
754 14 100       152 $valid = E({ %$state, _schema_path_suffix => $property },
755             'missing propert%s: %s', @missing > 1 ? 'ies' : 'y', join(', ', @missing));
756             }
757             }
758             else {
759             # as in dependentSchemas
760             next if not exists $data->{$property}
761             or _eval_subschema($data, $schema->{dependencies}{$property},
762 106 100 100     478 +{ %$state, schema_path => jsonp($state->{schema_path}, 'dependencies', $property) });
763              
764 43         179 $valid = 0;
765 43 100       144 last if $state->{short_circuit};
766             }
767             }
768              
769 111 100       399 return 1 if $valid;
770 55         120 return E($state, 'not all dependencies are satisfied');
771             }
772              
773             # drafts 4, 6, 7, 2019-09:
774             # prefixItems: ignored
775             # items - array-based - start at 0; set $state->{_last_items_index} to last evaluated (not successfully).
776             # items - schema-based - start at 0; set $state->{_last_items_index} to last data item.
777             # booleans NOT accepted in draft4.
778             # additionalItems - schema-based. consume $state->{_last_items_index} as starting point.
779             # booleans accepted in all versions.
780              
781             # draft2020-12:
782             # prefixItems - array-based - start at 0; set $state->{_last_items_index} to last evaluated (not successfully).
783             # items - array-based: error
784             # items - schema-based - consume $state->{_last_items_index} as starting point.
785             # additionalItems - ignored
786              
787             # no $SPECIFICATION_VERSION specified:
788             # prefixItems - array-based - set $state->{_last_items_index} to last evaluated (not successfully).
789             # items - array-based - starting index is always 0
790             # set $state->{_last_items_index} to last evaluated (not successfully).
791             # items - schema-based - consume $state->{_last_items_index} as starting point
792             # set $state->{_last_items_index} to last data item.
793             # booleans accepted.
794             # additionalItems - schema-based. consume $state->{_last_items_index} as starting point.
795             # booleans accepted.
796              
797             # prefixItems + items(array-based): items will generate an error
798             # prefixItems + additionalItems: additionalItems will be ignored
799             # items(schema-based) + additionalItems: additionalItems does nothing.
800              
801 395     395   583 sub _eval_keyword_prefixItems ($data, $schema, $state) {
  395         633  
  395         576  
  395         512  
  395         536  
802 395 50       955 return if not assert_array_schemas($schema, $state);
803 395         1343 goto \&_eval_keyword__items_array_schemas;
804             }
805              
806 1284     1284   1892 sub _eval_keyword_items ($data, $schema, $state) {
  1284         1944  
  1284         1958  
  1284         1798  
  1284         1662  
807 1284 100       3208 if (is_plain_arrayref($schema->{items})) {
808             abort($state, 'array form of "items" not supported in %s', $state->{spec_version})
809 684 100 100     1957 if ($state->{spec_version}//'') eq 'draft2020-12';
810              
811 683         2371 goto \&_eval_keyword__items_array_schemas;
812             }
813              
814 600   100     2439 $state->{_last_items_index} //= -1;
815 600         2163 goto \&_eval_keyword__items_schema;
816             }
817              
818 215     215   316 sub _eval_keyword_additionalItems ($data, $schema, $state) {
  215         322  
  215         293  
  215         297  
  215         306  
819 215 100       542 return 1 if not exists $state->{_last_items_index};
820 183         627 goto \&_eval_keyword__items_schema;
821             }
822              
823             # prefixItems (draft 2020-12), array-based items (all drafts)
824 1078     1078   1637 sub _eval_keyword__items_array_schemas ($data, $schema, $state) {
  1078         1630  
  1078         1493  
  1078         1481  
  1078         1489  
825 1078 50       2738 abort($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
826 1078 100       2115 return 1 if not is_type('array', $data);
827              
828 865         1614 my $valid = 1;
829              
830 865         2479 foreach my $idx (0..$data->$#*) {
831 1519 100       5380 last if $idx > $schema->{$state->{keyword}}->$#*;
832 1250         2556 $state->{_last_items_index} = $idx;
833              
834 1250 100       2978 if (is_type('boolean', $schema->{$state->{keyword}}[$idx])) {
835 274 100       3421 next if $schema->{$state->{keyword}}[$idx];
836 108         1866 $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx,
837             _schema_path_suffix => $idx }, 'item not permitted');
838             }
839             else {
840             next if _eval_subschema($data->[$idx], $schema->{$state->{keyword}}[$idx],
841             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
842 976 100       15219 schema_path => $state->{schema_path}.'/'.$state->{keyword}.'/'.$idx });
843             }
844              
845 175         686 $valid = 0;
846             last if $state->{short_circuit} and not exists $schema->{
847             $state->{keyword} eq 'prefixItems' ? 'items'
848 175 50 100     1270 : $state->{keyword} eq 'items' ? 'additionalItems' : die
    100          
    100          
849             };
850             }
851              
852 865 100       2294 return E($state, 'not all items are valid') if not $valid;
853 693         1561 return 1;
854             }
855              
856             # schema-based items (all drafts), and additionalItems (drafts 4,6,7,2019-09)
857 783     783   1261 sub _eval_keyword__items_schema ($data, $schema, $state) {
  783         1094  
  783         1025  
  783         1055  
  783         1020  
858 783 100       1640 return 1 if not is_type('array', $data);
859 679 100       2145 return 1 if $state->{_last_items_index} == $data->$#*;
860              
861 441         782 my $valid = 1;
862 441         1412 foreach my $idx ($state->{_last_items_index}+1 .. $data->$#*) {
863 667 100 100     1988 if (is_type('boolean', $schema->{$state->{keyword}})
864             and ($state->{keyword} eq 'additionalItems')) {
865 26 100       441 next if $schema->{$state->{keyword}};
866             $valid = E({ %$state, data_path => $state->{data_path}.'/'.$idx },
867             '%sitem not permitted',
868 20 50 33     443 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '');
869             }
870             else {
871             next if _eval_subschema($data->[$idx], $schema->{$state->{keyword}},
872             +{ %$state, data_path => $state->{data_path}.'/'.$idx,
873 641 100       10975 schema_path => $state->{schema_path}.'/'.$state->{keyword} });
874 216         905 $valid = 0;
875             }
876              
877 236 100       923 last if $state->{short_circuit};
878             }
879              
880 376         1117 $state->{_last_items_index} = $data->$#*;
881              
882             return E($state, 'subschema is not valid against all %sitems',
883 376 100 100     1648 exists $schema->{prefixItems} || $state->{keyword} eq 'additionalItems' ? 'additional ' : '')
    100          
884             if not $valid;
885 179         427 return 1;
886             }
887              
888 709     709   1104 sub _eval_keyword_contains ($data, $schema, $state) {
  709         1077  
  709         998  
  709         972  
  709         908  
889 709 100       1455 return 1 if not is_type('array', $data);
890              
891 496         1178 $state->{_num_contains} = 0;
892 496         762 my @errors;
893 496         1427 foreach my $idx (0..$data->$#*) {
894 614 100       7631 if (_eval_subschema($data->[$idx], $schema->{contains},
895             +{ %$state, errors => \@errors,
896             data_path => $state->{data_path}.'/'.$idx,
897             schema_path => $state->{schema_path}.'/contains' })) {
898 385         2635 ++$state->{_num_contains};
899              
900             last if $state->{short_circuit}
901             and (not exists $schema->{maxContains} or $state->{_num_contains} > $schema->{maxContains})
902 385 100 100     2665 and ($state->{_num_contains} >= ($schema->{minContains}//1));
      100        
      100        
      100        
903             }
904             }
905              
906             # note: no items contained is only valid when minContains is explicitly 0
907 496 100 66     4475 if (not $state->{_num_contains} and (($schema->{minContains}//1) > 0
      66        
908             or $state->{spec_version} and $state->{spec_version} eq 'draft7')) {
909 195         455 push $state->{errors}->@*, @errors;
910 195         506 return E($state, 'subschema is not valid against any item');
911             }
912              
913 301         874 return 1;
914             }
915              
916 2283     2283   3566 sub _eval_keyword_properties ($data, $schema, $state) {
  2283         3690  
  2283         3181  
  2283         3343  
  2283         3131  
917 2283         5583 assert_keyword_type($state, $schema, 'object');
918 2283 100       4969 return 1 if not is_type('object', $data);
919              
920 2032         3624 my $valid = 1;
921 2032         7367 foreach my $property (sort keys $schema->{properties}->%*) {
922 2587 100       6409 next if not exists $data->{$property};
923              
924 1586 100       3606 if (is_type('boolean', $schema->{properties}{$property})) {
925 323 100       4296 next if $schema->{properties}{$property};
926 106         1274 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
927             _schema_path_suffix => $property }, 'property not permitted');
928             }
929             else {
930             next if _eval_subschema($data->{$property}, $schema->{properties}{$property},
931             +{ %$state,
932             data_path => jsonp($state->{data_path}, $property),
933 1263 100       11841 schema_path => jsonp($state->{schema_path}, 'properties', $property) });
934              
935 315         1282 $valid = 0;
936             }
937 421 100       1708 last if $state->{short_circuit};
938             }
939              
940 1885 100       6413 return E($state, 'not all properties are valid') if not $valid;
941 1486         3248 return 1;
942             }
943              
944 809     809   1320 sub _eval_keyword_patternProperties ($data, $schema, $state) {
  809         1282  
  809         1188  
  809         1192  
  809         1123  
945 809         2051 assert_keyword_type($state, $schema, 'object');
946              
947 809         3877 foreach my $property (sort keys $schema->{patternProperties}->%*) {
948 1250         8808 assert_pattern({ %$state, _schema_path_suffix => $property }, $property);
949             }
950              
951 807 100       1937 return 1 if not is_type('object', $data);
952              
953 614         1242 my $valid = 1;
954 614         2014 foreach my $property_pattern (sort keys $schema->{patternProperties}->%*) {
955 898         10397 foreach my $property (sort grep m/(?:$property_pattern)/, keys %$data) {
956 557 100       2441 if (is_type('boolean', $schema->{patternProperties}{$property_pattern})) {
957 319 100       4436 next if $schema->{patternProperties}{$property_pattern};
958 108         1304 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property),
959             _schema_path_suffix => $property_pattern }, 'property not permitted');
960             }
961             else {
962             next if _eval_subschema($data->{$property}, $schema->{patternProperties}{$property_pattern},
963             +{ %$state,
964             data_path => jsonp($state->{data_path}, $property),
965 238 100       2395 schema_path => jsonp($state->{schema_path}, 'patternProperties', $property_pattern) });
966              
967 87         350 $valid = 0;
968             }
969 195 100       928 last if $state->{short_circuit};
970             }
971             }
972              
973 614 100       3397 return E($state, 'not all properties are valid') if not $valid;
974 434         1061 return 1;
975             }
976              
977 719     719   1060 sub _eval_keyword_additionalProperties ($data, $schema, $state) {
  719         1149  
  719         1008  
  719         1015  
  719         988  
978 719 100       1453 return 1 if not is_type('object', $data);
979              
980 520         1081 my $valid = 1;
981 520         1598 foreach my $property (sort keys %$data) {
982 503 100 100     1580 next if exists $schema->{properties} and exists $schema->{properties}{$property};
983             next if exists $schema->{patternProperties}
984 393 100 100 148   1725 and any { $property =~ /(?:$_)/ } keys $schema->{patternProperties}->%*;
  148         1522  
985              
986 305 100       787 if (is_type('boolean', $schema->{additionalProperties})) {
987 164 100       2175 next if $schema->{additionalProperties};
988              
989 148         1722 $valid = E({ %$state, data_path => jsonp($state->{data_path}, $property) },
990             'additional property not permitted');
991             }
992             else {
993             next if _eval_subschema($data->{$property}, $schema->{additionalProperties},
994             +{ %$state,
995             data_path => jsonp($state->{data_path}, $property),
996 141 100       1357 schema_path => $state->{schema_path}.'/additionalProperties' });
997              
998 37         140 $valid = 0;
999             }
1000 185 100       972 last if $state->{short_circuit};
1001             }
1002              
1003 468 100       2056 return E($state, 'not all additional properties are valid') if not $valid;
1004 284         627 return 1;
1005             }
1006              
1007 413     413   658 sub _eval_keyword_propertyNames ($data, $schema, $state) {
  413         691  
  413         617  
  413         638  
  413         605  
1008 413 100       893 return 1 if not is_type('object', $data);
1009              
1010 238         515 my $valid = 1;
1011 238         760 foreach my $property (sort keys %$data) {
1012             next if _eval_subschema($property, $schema->{propertyNames},
1013             +{ %$state,
1014             data_path => jsonp($state->{data_path}, $property),
1015 154 100       734 schema_path => $state->{schema_path}.'/propertyNames' });
1016              
1017 104         432 $valid = 0;
1018 104 100       368 last if $state->{short_circuit};
1019             }
1020              
1021 238 100       812 return E($state, 'not all property names are valid') if not $valid;
1022 134         320 return 1;
1023             }
1024              
1025 356     356   557 sub _eval_keyword_unevaluatedItems ($data, $schema, $state) {
  356         558  
  356         519  
  356         553  
  356         486  
1026 356         782 abort($state, 'keyword not yet supported');
1027             }
1028              
1029 549     549   837 sub _eval_keyword_unevaluatedProperties ($data, $schema, $state) {
  549         896  
  549         793  
  549         760  
  549         726  
1030 549         1211 abort($state, 'keyword not yet supported');
1031             }
1032              
1033             # UTILITIES
1034              
1035 50491     50491 0 399008 sub is_type ($type, $value) {
  50491         72326  
  50491         70025  
  50491         64365  
1036 50491 100       91442 if ($type eq 'null') {
1037 71         313 return !(defined $value);
1038             }
1039 50420 100       89508 if ($type eq 'boolean') {
1040 5295         16830 return is_bool($value);
1041             }
1042 45125 100       79162 if ($type eq 'object') {
1043 11605         36405 return is_plain_hashref($value);
1044             }
1045 33520 100       58869 if ($type eq 'array') {
1046 8569         27527 return is_plain_arrayref($value);
1047             }
1048              
1049 24951 100 100     72151 if ($type eq 'string' or $type eq 'number' or $type eq 'integer') {
      100        
1050 24933 100       45665 return 0 if not defined $value;
1051 24915         94199 my $flags = B::svref_2object(\$value)->FLAGS;
1052              
1053 24915 100       59374 if ($type eq 'string') {
1054 15981   66     99957 return !is_ref($value) && $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK));
1055             }
1056              
1057 8934 100       16862 if ($type eq 'number') {
1058 6067   100     32624 return ref($value) =~ /^Math::Big(?:Int|Float)$/
1059             || !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK));
1060             }
1061              
1062 2867 50       5633 if ($type eq 'integer') {
1063 2867   100     23187 return ref($value) =~ /^Math::Big(?:Int|Float)$/ && $value->is_int
1064             || !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK)) && int($value) == $value;
1065             }
1066             }
1067              
1068 18 100       95 if ($type =~ /^reference to (.+)$/) {
1069 14   33     123 return !blessed($value) && ref($value) eq $1;
1070             }
1071              
1072 4         22 return ref($value) eq $type;
1073             }
1074              
1075 33338     33338 0 110469 sub get_type ($value) {
  33338         51386  
  33338         43603  
1076 33338 100       68559 return 'null' if not defined $value;
1077 33050 100       85899 return 'object' if is_plain_hashref($value);
1078 11958 100       22667 return 'array' if is_plain_arrayref($value);
1079 10847 100       27523 return 'boolean' if is_bool($value);
1080              
1081 8905 100       61986 return ref($value) =~ /^Math::Big(?:Int|Float)$/ ? ($value->is_int ? 'integer' : 'number')
    100          
    100          
    100          
1082             : (blessed($value) ? '' : 'reference to ').ref($value)
1083             if is_ref($value);
1084              
1085 8475         25308 my $flags = B::svref_2object(\$value)->FLAGS;
1086 8475 100 100     30539 return 'string' if $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK));
1087 4187 100 66     22027 return int($value) == $value ? 'integer' : 'number'
    100          
1088             if !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK));
1089              
1090 1         8 croak sprintf('ambiguous type for %s',
1091             JSON::MaybeXS->new(allow_nonref => 1, canonical => 1, utf8 => 0)->encode($value));
1092             }
1093              
1094             # compares two arbitrary data payloads for equality, as per
1095             # https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.2.2
1096             # if provided with a state hashref, any differences are recorded within
1097 3869     3869 0 5626 sub is_equal ($x, $y, $state = {}) {
  3869         5572  
  3869         5362  
  3869         5711  
  3869         5234  
1098 3869   100     15762 $state->{path} //= '';
1099              
1100 3869         8474 my @types = map get_type($_), $x, $y;
1101              
1102 3869 100       12034 if ($SCALARREF_BOOLEANS) {
1103 93 100       200 ($x, $types[0]) = (0+!!$$x, 'boolean') if $types[0] eq 'reference to SCALAR';
1104 93 100       184 ($y, $types[1]) = (0+!!$$y, 'boolean') if $types[1] eq 'reference to SCALAR';
1105             }
1106              
1107 3869 100       8936 return 0 if $types[0] ne $types[1];
1108 3052 100       5683 return 1 if $types[0] eq 'null';
1109 3038 100       9735 return $x eq $y if $types[0] eq 'string';
1110 1609 100       6941 return $x == $y if grep $types[0] eq $_, qw(boolean number integer);
1111              
1112 530         921 my $path = $state->{path};
1113 530 100       1095 if ($types[0] eq 'object') {
1114 208 100       618 return 0 if keys %$x != keys %$y;
1115 192 100       882 return 0 if not is_equal([ sort keys %$x ], [ sort keys %$y ]);
1116 186         703 foreach my $property (sort keys %$x) {
1117 218         475 $state->{path} = jsonp($path, $property);
1118 218 100       607 return 0 if not is_equal($x->{$property}, $y->{$property}, $state);
1119             }
1120 100         577 return 1;
1121             }
1122              
1123 322 50       777 if ($types[0] eq 'array') {
1124 322 100       747 return 0 if @$x != @$y;
1125 314         760 foreach my $idx (0..$x->$#*) {
1126 352         921 $state->{path} = $path.'/'.$idx;
1127 352 100       849 return 0 if not is_equal($x->[$idx], $y->[$idx], $state);
1128             }
1129 226         908 return 1;
1130             }
1131              
1132 0         0 return 0; # should never get here
1133             }
1134              
1135             # checks array elements for uniqueness. short-circuits on first pair of matching elements
1136             # if second arrayref is provided, it is populated with the indices of identical items
1137 2344     2344 0 3619 sub is_elements_unique ($array, $equal_indices = undef) {
  2344         3310  
  2344         3714  
  2344         3285  
1138 2344         6738 foreach my $idx0 (0..$array->$#*-1) {
1139 836         1957 foreach my $idx1 ($idx0+1..$array->$#*) {
1140 1241 100       2814 if (is_equal($array->[$idx0], $array->[$idx1])) {
1141 201 50       1884 push @$equal_indices, $idx0, $idx1 if defined $equal_indices;
1142 201         637 return 0;
1143             }
1144             }
1145             }
1146 2143         5840 return 1;
1147             }
1148              
1149             # shorthand for creating and appending json pointers
1150             sub jsonp {
1151 43850 100   43850 0 408316 return join('/', shift, map s/~/~0/gr =~ s!/!~1!gr, map +(is_plain_arrayref($_) ? @$_ : $_), grep defined, @_);
1152             }
1153              
1154             # shorthand for finding the canonical uri of the present schema location
1155 30398     30398 0 44024 sub canonical_uri ($state, @extra_path) {
  30398         43608  
  30398         54722  
  30398         41872  
1156 30398 100 100     84523 splice(@extra_path, -1, 1, $extra_path[-1]->@*) if @extra_path and is_plain_arrayref($extra_path[-1]);
1157 30398         92434 my $uri = $state->{initial_schema_uri}->clone;
1158 30398   100     1000679 $uri->fragment(($uri->fragment//'').jsonp($state->{schema_path}, @extra_path));
1159 30398 100       210891 $uri->fragment(undef) if not length($uri->fragment);
1160 30398         209318 $uri;
1161             }
1162              
1163             # shorthand for creating error objects
1164 9416     9416 0 31697 sub E ($state, $error_string, @args) {
  9416         13845  
  9416         13836  
  9416         15023  
  9416         12001  
1165             # sometimes the keyword shouldn't be at the very end of the schema path
1166 9416         30645 my $uri = canonical_uri($state, $state->{keyword}, $state->{_schema_path_suffix});
1167              
1168             my $keyword_location = $state->{traversed_schema_path}
1169 9416         29587 .jsonp($state->{schema_path}, $state->{keyword}, delete $state->{_schema_path_suffix});
1170              
1171 9416 100 100     35601 undef $uri if $uri eq '' and $keyword_location eq ''
      100        
      100        
      100        
1172             or ($uri->fragment//'') eq $keyword_location and $uri->clone->fragment(undef) eq '';
1173              
1174             push $state->{errors}->@*, {
1175             instanceLocation => $state->{data_path},
1176 9416 100       3507600 keywordLocation => $keyword_location,
    100          
1177             defined $uri ? ( absoluteKeywordLocation => $uri->to_string) : (),
1178             error => @args ? sprintf($error_string, @args) : $error_string,
1179             };
1180              
1181 9416         262715 return 0;
1182             }
1183              
1184             # creates an error object, but also aborts evaluation immediately
1185             # only this error is returned, because other errors on the stack might not actually be "real"
1186             # errors (consider if we were in the middle of evaluating a "not" or "if")
1187 1607     1607 0 294228 sub abort ($state, $error_string, @args) {
  1607         2497  
  1607         2393  
  1607         2857  
  1607         2519  
1188 1607         4409 E($state, $error_string, @args);
1189 1607         21403 die pop $state->{errors}->@*;
1190             }
1191              
1192             # one common usecase of abort()
1193 26795     26795 0 38008 sub assert_keyword_type ($state, $schema, $type) {
  26795         37628  
  26795         34960  
  26795         38838  
  26795         34914  
1194 26795         52573 my $value = $schema->{$state->{keyword}};
1195             $value = is_plain_hashref($value) ? $value->{$state->{_schema_path_suffix}}
1196             : is_plain_arrayref($value) ? $value->[$state->{_schema_path_suffix}]
1197             : die 'unknown type'
1198 26795 0       55385 if exists $state->{_schema_path_suffix};
    50          
    100          
1199 26795 100       52904 return 1 if is_type($type, $value);
1200 8 100       57 abort($state, '%s value is not a%s %s', $state->{keyword}, ($type =~ /^[aeiou]/ ? 'n' : ''), $type);
1201             }
1202              
1203 2145     2145 0 3182 sub assert_pattern ($state, $pattern) {
  2145         3037  
  2145         3040  
  2145         2736  
1204             try {
1205 0     0   0 local $SIG{__WARN__} = sub { die @_ };
1206             qr/$pattern/;
1207             }
1208 2145         4543 catch ($e) { abort($state, $e); };
1209 2142         15699 return 1;
1210             }
1211              
1212 2279     2279 0 3229 sub assert_uri_reference ($state, $schema) {
  2279         3673  
  2279         3182  
  2279         3183  
1213 2279         4624 my $ref = $schema->{$state->{keyword}};
1214              
1215             abort($state, '%s value is not a valid URI reference', $state->{keyword})
1216             # see also uri-reference format sub
1217 2279 50 33     6925 if fc(Mojo::URL->new($ref)->to_unsafe_string) ne fc($ref)
      100        
      100        
      66        
      33        
1218             or $ref =~ /[^[:ascii:]]/
1219             or $ref =~ /#/
1220             and $ref !~ m{#$} # empty fragment
1221             and $ref !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
1222             and $ref !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
1223              
1224 2279         788984 return 1;
1225             }
1226              
1227 5216     5216 0 6914 sub assert_uri ($state, $schema, $override = undef) {
  5216         7296  
  5216         7392  
  5216         7680  
  5216         6703  
1228 5216   66     17185 my $string = $override // $schema->{$state->{keyword}};
1229 5216         15416 my $uri = Mojo::URL->new($string);
1230              
1231 5216 0 33     427852 abort($state, '"%s" is not a valid URI', $string)
      33        
      66        
      33        
      33        
      33        
1232             # see also uri format sub
1233             if fc($uri->to_unsafe_string) ne fc($string)
1234             or $string =~ /[^[:ascii:]]/
1235             or not $uri->is_abs
1236             or $string =~ /#/
1237             and $string !~ m{#$} # empty fragment
1238             and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment
1239             and $string !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment
1240              
1241 5216         1022613 return 1;
1242             }
1243              
1244 2780     2780 0 4170 sub assert_non_negative_integer ($schema, $state) {
  2780         3912  
  2780         3894  
  2780         3747  
1245 2780         6536 assert_keyword_type($state, $schema, 'integer');
1246             abort($state, '%s value is not a non-negative integer', $state->{keyword})
1247 2780 50       9163 if $schema->{$state->{keyword}} < 0;
1248 2780         24868 return 1;
1249             }
1250              
1251 1886     1886 0 2672 sub assert_array_schemas ($schema, $state) {
  1886         2980  
  1886         2880  
  1886         2780  
1252 1886         4641 assert_keyword_type($state, $schema, 'array');
1253 1886 50       5252 abort($state, '%s array is empty', $state->{keyword}) if not $schema->{$state->{keyword}}->@*;
1254 1886         3122 return 1;
1255             }
1256              
1257 971     971 0 1525 sub sprintf_num ($value) {
  971         1545  
  971         1369  
1258             # use original value as stored in the NV, without losing precision
1259 971 100       4625 ref($value) =~ /^Math::Big(?:Int|Float)$/ ? $value->bstr : sprintf('%s', $value);
1260             }
1261              
1262             1;
1263              
1264             __END__
1265              
1266             =pod
1267              
1268             =encoding UTF-8
1269              
1270             =for stopwords schema subschema metaschema validator evaluator
1271              
1272             =head1 NAME
1273              
1274             JSON::Schema::Tiny - Validate data against a schema, minimally
1275              
1276             =head1 VERSION
1277              
1278             version 0.021
1279              
1280             =head1 SYNOPSIS
1281              
1282             my $data = { hello => 1 };
1283             my $schema = {
1284             type => "object",
1285             properties => { hello => { type => "integer" } },
1286             };
1287              
1288             # functional interface:
1289             use JSON::Schema::Tiny qw(evaluate);
1290             my $result = evaluate($data, $schema); # { valid => true }
1291              
1292             # object-oriented interface:
1293             use JSON::Schema::Tiny;
1294             my $js = JSON::Schema::Tiny->new;
1295             my $result = $js->evaluate($data, $schema); # { valid => true }
1296              
1297             =head1 DESCRIPTION
1298              
1299             This module aims to be a slimmed-down L<JSON Schema|https://json-schema.org/> evaluator and
1300             validator, supporting the most popular keywords.
1301             (See L</UNSUPPORTED JSON-SCHEMA FEATURES> below for exclusions.)
1302              
1303             =head1 FUNCTIONS
1304              
1305             =for Pod::Coverage is_type get_type is_equal is_elements_unique jsonp canonical_uri E abort
1306             assert_keyword_type assert_pattern assert_uri assert_non_negative_integer assert_array_schemas
1307             new assert_uri_reference sprintf_num
1308              
1309             =head2 evaluate
1310              
1311             my $result = evaluate($data, $schema);
1312              
1313             Evaluates the provided instance data against the known schema document.
1314              
1315             The data is in the form of an unblessed nested Perl data structure representing any type that JSON
1316             allows: null, boolean, string, number, object, array. (See L</TYPES> below.)
1317              
1318             The schema must represent a valid JSON Schema in the form of a Perl data structure, such as what is
1319             returned from a JSON decode operation.
1320              
1321             With default configuration settings, the return value is a hashref indicating the validation success
1322             or failure, plus (when validation failed), an arrayref of error strings in standard JSON Schema
1323             format. For example:
1324              
1325             running:
1326              
1327             $result = evaluate(1, { type => 'number' });
1328              
1329             C<$result> is:
1330              
1331             { valid => true }
1332              
1333             running:
1334              
1335             $result = evaluate(1, { type => 'number', multipleOf => 2 });
1336              
1337             C<$result> is:
1338              
1339             {
1340             valid => false,
1341             errors => [
1342             {
1343             instanceLocation => '',
1344             keywordLocation => '/multipleOf',
1345             error => 'value is not a multiple of 2',
1346             },
1347             ],
1348             }
1349              
1350             When L</C<$BOOLEAN_RESULT>> is true, the return value is a boolean (indicating evaluation success or
1351             failure).
1352              
1353             =head1 OPTIONS
1354              
1355             All options are available as package-scoped global variables. Use L<local|perlfunc/local> to
1356             configure them for a local scope. They may also be set via the constructor, as lower-cased values in
1357             a hash, e.g.: C<< JSON::Schema::Tiny->new(boolean_result => 1, max_traversal_depth => 10); >>
1358              
1359             =head2 C<$BOOLEAN_RESULT>
1360              
1361             When true, L</evaluate> will return a true or false result only, with no error strings. This enables
1362             short-circuit mode internally as this cannot effect results except get there faster. Defaults to false.
1363              
1364             =head2 C<$SHORT_CIRCUIT>
1365              
1366             When true, L</evaluate> will return from evaluating each subschema as soon as a true or false result
1367             can be determined. When C<$BOOLEAN_RESULT> is false, an incomplete list of errors will be returned.
1368             Defaults to false.
1369              
1370             =head2 C<$MAX_TRAVERSAL_DEPTH>
1371              
1372             The maximum number of levels deep a schema traversal may go, before evaluation is halted. This is to
1373             protect against accidental infinite recursion, such as from two subschemas that each reference each
1374             other, or badly-written schemas that could be optimized. Defaults to 50.
1375              
1376             =head2 C<$SCALARREF_BOOLEANS>
1377              
1378             When true, any type that is expected to be a boolean B<in the instance data> may also be expressed as
1379             the scalar references C<\0> or C<\1> (which are serialized as booleans by JSON backends).
1380             Defaults to false.
1381              
1382             =head2 C<$SPECIFICATION_VERSION>
1383              
1384             When set, the version of the draft specification is locked to one particular value, and use of
1385             keywords inconsistent with that specification version will result in an error. Will be set
1386             internally automatically with the use of the C<$schema> keyword. When not set, all keywords will be
1387             honoured (when otherwise supported).
1388              
1389             Supported values for this option, and the corresponding values for the C<$schema> keyword, are:
1390              
1391             =over 4
1392              
1393             =item *
1394              
1395             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>
1396              
1397             =item *
1398              
1399             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>
1400              
1401             =item *
1402              
1403             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#>
1404              
1405             =back
1406              
1407             Defaults to undef.
1408              
1409             =head1 UNSUPPORTED JSON-SCHEMA FEATURES
1410              
1411             Unlike L<JSON::Schema::Modern>, this is not a complete implementation of the JSON Schema
1412             specification. Some features and keywords are left unsupported in order to keep the code small and
1413             the execution fast. These features are not available:
1414              
1415             =over 4
1416              
1417             =item *
1418              
1419             any output format other than C<flag> (when C<$BOOLEAN_RESULT> is true) or C<basic> (when it is false)
1420              
1421             =item *
1422              
1423             L<annotations|https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.7.7> in successful evaluation results
1424              
1425             =item *
1426              
1427             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.
1428              
1429             =back
1430              
1431             In addition, these keywords are implemented only partially or not at all (their presence in a schema
1432             will be ignored or possibly result in an error):
1433              
1434             =over 4
1435              
1436             =item *
1437              
1438             C<$schema> - only accepted if set to one of the specification metaschema URIs (see L<$SPECIFICATION_VERSION> for supported values)
1439              
1440             =item *
1441              
1442             C<$id>
1443              
1444             =item *
1445              
1446             C<$anchor>
1447              
1448             =item *
1449              
1450             C<$recursiveAnchor> and C<$recursiveRef> (draft2019-09), and C<$dynamicAnchor> and C<$dynamicRef> (draft2020-12 and thereafter)
1451              
1452             =item *
1453              
1454             C<$vocabulary>
1455              
1456             =item *
1457              
1458             C<unevaluatedItems> and C<unevaluatedProperties> (which require annotation support)
1459              
1460             =item *
1461              
1462             C<format> (does not cause an error when used)
1463              
1464             =back
1465              
1466             For a more full-featured implementation of the JSON Schema specification, see
1467             L<JSON::Schema::Modern>.
1468              
1469             =head1 LIMITATIONS
1470              
1471             =head2 Types
1472              
1473             Perl is a more loosely-typed language than JSON. This module delves into a value's internal
1474             representation in an attempt to derive the true "intended" type of the value. However, if a value is
1475             used in another context (for example, a numeric value is concatenated into a string, or a numeric
1476             string is used in an arithmetic operation), additional flags can be added onto the variable causing
1477             it to resemble the other type. This should not be an issue if data validation is occurring
1478             immediately after decoding a JSON (or YAML) payload.
1479              
1480             For more information, see L<Cpanel::JSON::XS/MAPPING>.
1481              
1482             =head1 SECURITY CONSIDERATIONS
1483              
1484             The C<pattern> and C<patternProperties> keywords evaluate regular expressions from the schema.
1485             No effort is taken (at this time) to sanitize the regular expressions for embedded code or
1486             potentially pathological constructs that may pose a security risk, either via denial of service
1487             or by allowing exposure to the internals of your application. B<DO NOT USE SCHEMAS FROM UNTRUSTED
1488             SOURCES.>
1489              
1490             =head1 SEE ALSO
1491              
1492             =over 4
1493              
1494             =item *
1495              
1496             L<JSON::Schema::Modern>: a more specification-compliant JSON Schema evaluator
1497              
1498             =item *
1499              
1500             L<Test::JSON::Schema::Acceptance>: contains the official JSON Schema test suite
1501              
1502             =item *
1503              
1504             L<https://json-schema.org>
1505              
1506             =item *
1507              
1508             L<Understanding JSON Schema|https://json-schema.org/understanding-json-schema>: tutorial-focused documentation
1509              
1510             =back
1511              
1512             =for stopwords OpenAPI
1513              
1514             =head1 SUPPORT
1515              
1516             Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Tiny/issues>.
1517              
1518             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
1519              
1520             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
1521             server|https://open-api.slack.com>, which are also great resources for finding help.
1522              
1523             =head1 AUTHOR
1524              
1525             Karen Etheridge <ether@cpan.org>
1526              
1527             =head1 CONTRIBUTOR
1528              
1529             =for stopwords Matt S Trout
1530              
1531             Matt S Trout <mst@shadowcat.co.uk>
1532              
1533             =head1 COPYRIGHT AND LICENCE
1534              
1535             This software is copyright (c) 2021 by Karen Etheridge.
1536              
1537             This is free software; you can redistribute it and/or modify it under
1538             the same terms as the Perl 5 programming language system itself.
1539              
1540             =cut