File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/Core.pm
Criterion Covered Total %
statement 259 259 100.0
branch 106 118 89.8
condition 59 66 89.3
subroutine 38 38 100.0
pod 0 3 0.0
total 462 484 95.4


line stmt bran cond sub pod time code
1 38     38   1566 use strict;
  38         90  
  38         1712  
2 38     38   414 use warnings;
  38         215  
  38         3818  
3             package JSON::Schema::Modern::Vocabulary::Core;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema Core vocabulary
6              
7             our $VERSION = '0.634';
8              
9 38     38   814 use 5.020;
  38         139  
10 38     38   262 use Moo;
  38         95  
  38         345  
11 38     38   17172 use strictures 2;
  38         417  
  38         1785  
12 38     38   19593 use stable 0.031 'postderef';
  38         831  
  38         341  
13 38     38   9373 use experimental 'signatures';
  38         78  
  38         180  
14 38     38   2668 no autovivification warn => qw(fetch store exists delete);
  38         90  
  38         323  
15 38     38   3478 use if "$]" >= 5.022, experimental => 're_strict';
  38         100  
  38         1239  
16 38     38   3736 no if "$]" >= 5.031009, feature => 'indirect';
  38         82  
  38         3481  
17 38     38   239 no if "$]" >= 5.033001, feature => 'multidimensional';
  38         73  
  38         2653  
18 38     38   228 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  38         79  
  38         2518  
19 38     38   219 no if "$]" >= 5.041009, feature => 'smartmatch';
  38         82  
  38         1948  
20 38     38   256 no feature 'switch';
  38         79  
  38         1803  
21 38     38   234 use JSON::Schema::Modern::Utilities qw(is_type abort assert_keyword_type canonical_uri E assert_uri_reference assert_uri jsonp);
  38         74  
  38         4660  
22 38     38   253 use namespace::clean;
  38         83  
  38         419  
23              
24             with 'JSON::Schema::Modern::Vocabulary';
25              
26 22     22 0 51 sub vocabulary ($class) {
  22         61  
  22         52  
27 22         263 'https://json-schema.org/draft/2019-09/vocab/core' => 'draft2019-09',
28             'https://json-schema.org/draft/2020-12/vocab/core' => 'draft2020-12';
29             }
30              
31 41     41 0 87 sub evaluation_order ($class) { 0 }
  41         1280  
  41         87  
  41         459  
32              
33 200     200 0 21478 sub keywords ($class, $spec_version) {
  200         478  
  200         575  
  200         400  
34             return (
35 200 100       13767 '$schema', # must be first to ensure we use the correct Core keywords and subsequent vocabularies
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
36             $spec_version eq 'draft4' ? 'id' : '$id',
37             $spec_version !~ /^draft[467]\z/ ? '$anchor' : (),
38             $spec_version eq 'draft2019-09' ? '$recursiveAnchor' : (),
39             $spec_version !~ /^draft(?:[467]|2019-09)\z/ ? '$dynamicAnchor' : (),
40             '$ref',
41             $spec_version eq 'draft2019-09' ? '$recursiveRef' : (),
42             $spec_version !~ /^draft(?:[467]|2019-09)\z/ ? '$dynamicRef' : (),
43             $spec_version !~ /^draft[467]\z/ ? '$vocabulary' : (),
44             $spec_version =~ /^draft[467]\z/ ? 'definitions' : '$defs',
45             $spec_version !~ /^draft[46]\z/ ? '$comment' : (),
46             );
47             }
48              
49             # adds the following keys to $state during traversal:
50             # - identifiers: an arrayref of tuples:
51             # $uri => { path => $path_to_identifier, canonical_uri => Mojo::URL (absolute when possible) }
52             # this is used by the Document constructor to build its resource_index.
53              
54 2153     2153   5085 sub _traverse_keyword_id ($class, $schema, $state) {
  2153         4867  
  2153         4281  
  2153         4062  
  2153         3947  
55 2153 100 66     12671 return if not assert_keyword_type($state, $schema, 'string')
56             or not assert_uri_reference($state, $schema);
57              
58 2151         15065 my $uri = Mojo::URL->new($schema->{$state->{keyword}});
59              
60 2151 100       309017 if (length $uri->fragment) {
61             return E($state, '%s value "%s" cannot have a non-empty fragment', $state->{keyword}, $schema->{$state->{keyword}})
62 98 100       1359 if $state->{specification_version} !~ /^draft[467]\z/;
63              
64 94 100       413 if (length(my $base = $uri->clone->fragment(undef))) {
65             return E($state, '$id cannot change the base uri at the same time as declaring an anchor')
66 15 100       4205 if $state->{specification_version} =~ /^draft[67]\z/;
67              
68             # only permitted in draft4: add an id and an anchor via the single 'id' keyword
69 13 50       77 return if not $class->__create_identifier($base, $state);
70             }
71              
72 92         16502 return $class->_traverse_keyword_anchor({ %$schema, id => '#'.$uri->fragment }, $state);
73             }
74              
75 2053 100       19867 return if not $class->__create_identifier($uri, $state);
76 2027         13582 return 1;
77             }
78              
79 2066     2066   4689 sub __create_identifier ($class, $uri, $state) {
  2066         4380  
  2066         3956  
  2066         3915  
  2066         4004  
80 2066         7297 $uri->fragment(undef);
81 2066 100       18277 return E($state, '%s cannot be empty', $state->{keyword}) if not length $uri;
82              
83 2042 100       424315 $uri = $uri->to_abs($state->{initial_schema_uri}) if not $uri->is_abs;
84              
85             return E($state, 'duplicate canonical uri "%s" found (original at path "%s")',
86             $uri, $state->{identifiers}{$uri}{path})
87 2042 100       447558 if exists $state->{identifiers}{$uri};
88              
89 2040         186232 $state->{initial_schema_uri} = $uri;
90 2040         10969 $state->{traversed_keyword_path} = $state->{traversed_keyword_path}.$state->{keyword_path};
91 2040         6417 $state->{keyword_path} = '';
92              
93             # Note that since '$schema' is considered ahead of '$id' in the keyword list, the dialect
94             # (specification_version and vocabularies) is known to be correct.
95              
96             $state->{identifiers}{$uri} = {
97             path => $state->{traversed_keyword_path},
98             canonical_uri => $uri,
99 2040         21619 $state->%{qw(specification_version vocabularies)},
100             };
101              
102 2040         435943 return 1;
103             }
104              
105 2708     2708   18360 sub _eval_keyword_id ($class, $, $schema, $state) {
  2708         6254  
  2708         5453  
  2708         5281  
  2708         5184  
106             # we already indexed the anchor uri, so there is nothing more to do at evaluation time.
107             # we explicitly do NOT set $state->{initial_schema_uri} or change any other $state values.
108             return 1
109 2708 100 100     22757 if $state->{specification_version} =~ /^draft[467]\z/ and $schema->{$state->{keyword}} =~ /^#/;
110              
111 2643         16410 my $schema_info = $state->{evaluator}->_fetch_from_uri($state->{initial_schema_uri}->clone->fragment($state->{keyword_path}));
112              
113             # this should never happen, if the pre-evaluation traversal was performed correctly
114 2643 50       19672 abort($state, 'failed to resolve "%s" to canonical uri', $state->{keyword}) if not $schema_info;
115              
116             # $state->{document} is set by evaluate() and does not change unless following a reference
117             abort($state, 'EXCEPTION: mismatched document when processing %s "%s"',
118             $state->{keyword}, $schema->{$state->{keyword}})
119 2643 50       15729 if $schema_info->{document} != $state->{document};
120              
121             # these will all be set when we are at the document root, or if we are here via a $ref,
122             # but not if we are organically passing through this subschema.
123 2643         10203 $state->{initial_schema_uri} = $schema_info->{canonical_uri};
124 2643         13168 $state->{traversed_keyword_path} = $state->{traversed_keyword_path}.$state->{keyword_path};
125 2643         8458 $state->{keyword_path} = '';
126 2643         12595 $state->@{qw(specification_version vocabularies)} = $schema_info->@{qw(specification_version vocabularies)};
127              
128             push $state->{dynamic_scope}->@*, $state->{initial_schema_uri}
129 2643 100       18358 if $state->{dynamic_scope}->[-1] ne $schema_info->{canonical_uri};
130              
131 2643         1100041 return 1;
132             }
133              
134 5957     5957   13982 sub _traverse_keyword_schema ($class, $schema, $state) {
  5957         14048  
  5957         14550  
  5957         11165  
  5957         11265  
135             # Note that this sub is sometimes called with $state->{keyword} undefined, in order to change
136             # error locations
137              
138             # Note that because this keyword is parsed ahead of "id"/"$id", location information may not
139             # be correct if an error occurs when parsing this keyword.
140 5957 100       30171 return E($state, '$schema value is not a string') if not is_type('string', $schema->{'$schema'});
141 5955 100       61580 return if not assert_uri($state, $schema, $schema->{'$schema'});
142              
143 5952         18592 my ($spec_version, $vocabularies);
144              
145 5952 100       61054 if (my $metaschema_info = $state->{evaluator}->_get_metaschema_vocabulary_classes($schema->{'$schema'})) {
146 5895         82250 ($spec_version, $vocabularies) = @$metaschema_info;
147             }
148             else {
149 57         1062 my $schema_info = $state->{evaluator}->_fetch_from_uri($schema->{'$schema'});
150 57 100       320 return E($state, 'EXCEPTION: unable to find resource "%s"', $schema->{'$schema'}) if not $schema_info;
151             # this cannot happen unless there are other entity types in the index
152             return E($state, 'EXCEPTION: bad reference to $schema "%s": not a schema', $schema_info->{canonical_uri})
153 55 100       437 if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
154              
155 54 100       338 if (ref $schema_info->{schema} ne 'HASH') {
156 1         5 ()= E($state, 'metaschemas must be objects');
157             }
158             else {
159             ($spec_version, $vocabularies) = $state->{evaluator}->_fetch_vocabulary_data({ %$state,
160             keyword => '$vocabulary', initial_schema_uri => Mojo::URL->new($schema->{'$schema'}),
161 53         634 traversed_keyword_path => jsonp($state->{traversed_keyword_path}.$state->{keyword_path}, $state->{keyword}) },
162             $schema_info);
163             }
164             }
165              
166 5949 100 100     33197 return E($state, '"%s" is not a valid metaschema', $schema->{'$schema'})
167             if not $vocabularies or not @$vocabularies;
168              
169             # "A JSON Schema resource is a schema which is canonically identified by an absolute URI."
170             # "A resource's root schema is its top-level schema object."
171             # note: we need not be at the document root, but simply adjacent to an $id (or be the at the
172             # document root)
173             return E($state, '$schema can only appear at the schema resource root')
174             if not exists $schema->{$spec_version eq 'draft4' ? 'id' : '$id'}
175 5929 100 100     60025 and length($state->{keyword_path});
    100          
176              
177             # This is a bit of a chicken-and-egg situation. If we start off at draft2020-12, then all
178             # keywords are valid, so we inspect and process the $schema keyword; this switches us to draft7
179             # but now only the $ref keyword is respected and everything else should be ignored, so the
180             # $schema keyword never happened, so now we're back to draft2020-12 again, and...?!
181             # The only winning move is not to play.
182             return E($state, '$schema and $ref cannot be used together in older drafts')
183 5925 100 100     36978 if exists $schema->{'$ref'} and $spec_version =~ /^draft[467]\z/;
184              
185 5924         47986 $state->{evaluator}->_set_metaschema_vocabulary_classes($schema->{'$schema'}, [ $spec_version, $vocabularies ]);
186 5924         2518541 $state->@{qw(specification_version vocabularies metaschema_uri)} = ($spec_version, $vocabularies, $schema->{'$schema'} =~ s/#\z//r);
187 5924         33768 return 1;
188             }
189              
190 6599     6599   13956 sub _eval_keyword_schema ($class, $, $schema, $state) {
  6599         13291  
  6599         14637  
  6599         11578  
  6599         10919  
191             # the dialect can change at any time, even in the middle of a document, where subsequent keywords
192             # and vocabularies can change; however if we came to this schema via a $ref it will already be
193             # set correctly
194 6599         63468 $state->@{qw(specification_version vocabularies)} = $state->{evaluator}->_get_metaschema_vocabulary_classes($schema->{'$schema'})->@*;
195 6599         120150 return 1;
196             }
197              
198 602     602   2161 sub _traverse_keyword_anchor ($class, $schema, $state) {
  602         1293  
  602         1157  
  602         1131  
  602         1094  
199 602 50       2511 return if not assert_keyword_type($state, $schema, 'string');
200              
201 602         2771 my $anchor = $schema->{$state->{keyword}};
202             return E($state, '%s value "%s" does not match required syntax', $state->{keyword}, $anchor)
203             if $state->{specification_version} =~ /^draft[467]\z/ and $anchor !~ /^#[A-Za-z][A-Za-z0-9_:.-]*\z/
204             or $state->{specification_version} eq 'draft2019-09' and $anchor !~ /^[A-Za-z][A-Za-z0-9_:.-]*\z/
205 602 100 100     11579 or $state->{specification_version} eq 'draft2020-12' and $anchor !~ /^[A-Za-z_][A-Za-z0-9._-]*\z/;
      100        
      100        
      100        
      100        
206              
207 592         2575 my $canonical_uri = canonical_uri($state);
208              
209 592 100       3926 $anchor =~ s/^#// if $state->{specification_version} =~ /^draft[467]\z/;
210 592         2823 my $uri = Mojo::URL->new->to_abs($canonical_uri)->fragment($anchor);
211 592         221896 my $base_uri = $canonical_uri->clone->fragment(undef);
212              
213 592 100       70742 if (exists $state->{identifiers}{$base_uri}) {
214             return E($state, 'duplicate anchor uri "%s" found (original at path "%s")',
215             $uri, $state->{identifiers}{$base_uri}{anchors}{$anchor}{path})
216 508 100 100     121256 if exists(($state->{identifiers}{$base_uri}{anchors}//{})->{$anchor});
217              
218 38     38   132214 use autovivification 'store';
  38         110  
  38         249  
219             $state->{identifiers}{$base_uri}{anchors}{$anchor} = {
220             canonical_uri => $canonical_uri,
221             path => $state->{traversed_keyword_path}.$state->{keyword_path},
222 507         115060 };
223             }
224             # we need not be at the root of the resource schema, and we need not even have an entry
225             # in 'identifiers' for our current base uri (if there was no $id at the root, or if
226             # initial_schema_uri was overridden in the call to traverse())
227             else {
228 84         442 my $base_path = '';
229 84 100       283 if (my $fragment = $canonical_uri->fragment) {
230             # this shouldn't happen, as we also check this at the start of traverse
231             return E($state, 'something is wrong; "%s" is not the suffix of "%s"', $fragment, $state->{traversed_keyword_path}.$state->{keyword_path})
232 77 50       1073 if substr($state->{traversed_keyword_path}.$state->{keyword_path}, -length($fragment))
233             ne $fragment;
234 77         414 $base_path = substr($state->{traversed_keyword_path}.$state->{keyword_path}, 0, -length($fragment));
235             }
236              
237             $state->{identifiers}{$base_uri} = {
238             # We didn't see an $id keyword at this position or above us, so a resource entry hasn't been
239             # made yet for this identifier. However, we have all the information we need to infer its
240             # data. If this entry is being created in a subschema (below the document root), another one
241             # just like it may be created by another subschema using the same base canonical uri, so that
242             # caller will need to merge the entries together before providing them to the Document's
243             # resource index.
244             canonical_uri => $base_uri,
245             path => $base_path,
246             $state->%{qw(specification_version vocabularies)},
247             anchors => {
248             $anchor => {
249             canonical_uri => $canonical_uri,
250             path => $state->{traversed_keyword_path}.$state->{keyword_path},
251             },
252             },
253 84         1475 };
254             }
255              
256 591         126259 return 1;
257             }
258              
259             # we already indexed the $anchor uri, so there is nothing more to do at evaluation time.
260             # we explicitly do NOT set $state->{initial_schema_uri}.
261              
262 182     182   428 sub _traverse_keyword_recursiveAnchor ($class, $schema, $state) {
  182         405  
  182         321  
  182         343  
  182         313  
263 182 50       806 return if not assert_keyword_type($state, $schema, 'boolean');
264              
265             # this is required because the location is used as the base URI for future resolution
266             # of $recursiveRef, and the fragment would be disregarded in the base
267             return E($state, '"$recursiveAnchor" keyword used without "$id"')
268 182 100       866 if length($state->{keyword_path});
269 179         576 return 1;
270             }
271              
272 379     379   1101 sub _eval_keyword_recursiveAnchor ($class, $, $schema, $state) {
  379         841  
  379         913  
  379         729  
  379         746  
273 379 100 100     2658 return 1 if not $schema->{'$recursiveAnchor'} or exists $state->{recursive_anchor_uri};
274              
275             # record the canonical location of the current position, to be used against future resolution
276             # of a $recursiveRef uri -- as if it was the current location when we encounter a $ref.
277 76         1307 $state->{recursive_anchor_uri} = canonical_uri($state);
278 76         334 return 1;
279             }
280              
281 296     296   713 sub _traverse_keyword_dynamicAnchor ($class, $schema, $state) {
  296         673  
  296         658  
  296         557  
  296         496  
282 296 50       1372 return if not $class->_traverse_keyword_anchor($schema, $state);
283 296         2207 $state->{identifiers}{$state->{initial_schema_uri}}{anchors}{$schema->{'$dynamicAnchor'}}{dynamic} = 1;
284             }
285              
286             # we already indexed the $dynamicAnchor uri, so there is nothing more to do at evaluation time.
287             # we explicitly do NOT set $state->{initial_schema_uri}.
288              
289 3733     3733   7873 sub _traverse_keyword_ref ($class, $schema, $state) {
  3733         7320  
  3733         6561  
  3733         6494  
  3733         6436  
290 3733 100 66     13465 return if not assert_keyword_type($state, $schema, 'string')
291             or not assert_uri_reference($state, $schema);
292              
293             push $state->{references}->@*, [
294             $state->{keyword},
295             ($state->{traversed_keyword_path}//'').$state->{keyword_path},
296             Mojo::URL->new($schema->{$state->{keyword}})->to_abs($state->{initial_schema_uri}//()),
297             'schema',
298 3701 100 50     52038 ] if $state->{references};
      33        
299              
300 3701         1733328 return 1;
301             }
302              
303 3640     3640   11197 sub _eval_keyword_ref ($class, $data, $schema, $state) {
  3640         9796  
  3640         9466  
  3640         7986  
  3640         8034  
  3640         7036  
304 3640         23489 my $uri = Mojo::URL->new($schema->{'$ref'})->to_abs($state->{initial_schema_uri});
305 3640         2316677 $class->eval_subschema_at_uri($data, $schema, $state, $uri);
306             }
307              
308             *_traverse_keyword_recursiveRef = \&_traverse_keyword_ref;
309              
310 108     108   273 sub _eval_keyword_recursiveRef ($class, $data, $schema, $state) {
  108         231  
  108         209  
  108         198  
  108         222  
  108         206  
311 108         633 my $uri = Mojo::URL->new($schema->{'$recursiveRef'})->to_abs($state->{initial_schema_uri});
312 108         57234 my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
313 108 50       528 abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
314             abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
315 108 100       651 if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
316              
317 107 100 100     1511 if (ref $schema_info->{schema} eq 'HASH'
      100        
318             and is_type('boolean', $schema_info->{schema}{'$recursiveAnchor'})
319             and $schema_info->{schema}{'$recursiveAnchor'}) {
320             $uri = Mojo::URL->new($schema->{'$recursiveRef'})
321 62   33     1079 ->to_abs($state->{recursive_anchor_uri} // $state->{initial_schema_uri});
322             }
323              
324 107         36303 return $class->eval_subschema_at_uri($data, $schema, $state, $uri);
325             }
326              
327             *_traverse_keyword_dynamicRef = \&_traverse_keyword_ref;
328              
329 214     214   468 sub __resolve_dynamicRef ($class, $uri, $state) {
  214         453  
  214         448  
  214         449  
  214         427  
330 214         1117 $uri = Mojo::URL->new($uri)->to_abs($state->{initial_schema_uri});
331 214         128573 my $schema_info = $state->{evaluator}->_fetch_from_uri($uri);
332 214 50       8069 abort($state, 'EXCEPTION: unable to find resource "%s"', $uri) if not $schema_info;
333             abort($state, 'EXCEPTION: bad reference to "%s": not a schema', $schema_info->{canonical_uri})
334 214 100       1386 if $schema_info->{document}->get_entity_at_location($schema_info->{document_path}) ne 'schema';
335              
336             # If the initially resolved starting point URI includes a fragment that was created by the
337             # "$dynamicAnchor" keyword, ...
338 213 100 100     864 if (length $uri->fragment
      100        
      100        
339             and ref $schema_info->{schema} eq 'HASH'
340             and exists $schema_info->{schema}{'$dynamicAnchor'}
341             and $uri->fragment eq (my $anchor = $schema_info->{schema}{'$dynamicAnchor'})) {
342             # ...the initial URI MUST be replaced by the URI (including the fragment) for the outermost
343             # schema resource in the dynamic scope that defines an identically named fragment with
344             # "$dynamicAnchor".
345 175         4943 foreach my $scope_uri ($state->{dynamic_scope}->@*) {
346 234         1197 my $resource = $state->{evaluator}->_get_or_load_resource($scope_uri);
347 234 50       906 die 'bad dynamic scope uri: ', $scope_uri if not $resource;
348 234 100 100     2846 if (exists(($resource->{anchors}//{})->{$anchor}) and $resource->{anchors}{$anchor}{dynamic}) {
      100        
349 170         808 $uri = Mojo::URL->new($scope_uri)->fragment($anchor);
350 170         58682 last;
351             }
352             }
353             }
354              
355 213         2022 return $uri;
356             }
357              
358 214     214   544 sub _eval_keyword_dynamicRef ($class, $data, $schema, $state) {
  214         564  
  214         454  
  214         417  
  214         369  
  214         464  
359 214         1515 my $uri = $class->__resolve_dynamicRef($schema->{'$dynamicRef'}, $state);
360 213         1474 return $class->eval_subschema_at_uri($data, $schema, $state, $uri);
361             }
362              
363 102     102   370 sub _traverse_keyword_vocabulary ($class, $schema, $state) {
  102         280  
  102         263  
  102         216  
  102         273  
364 102 50       491 return if not assert_keyword_type($state, $schema, 'object');
365              
366             return E($state, '$vocabulary can only appear at the schema resource root')
367 102 100       579 if length($state->{keyword_path});
368              
369 101         246 my $valid = 1;
370              
371 101         250 my @vocabulary_classes;
372 101         908 foreach my $uri (sort keys $schema->{'$vocabulary'}->%*) {
373 249 100       1418 if (not is_type('boolean', $schema->{'$vocabulary'}{$uri})) {
374 5         70 ()= E({ %$state, _keyword_path_suffix => $uri }, '$vocabulary value at "%s" is not a boolean', $uri);
375 5         39 $valid = 0;
376 5         18 next;
377             }
378              
379 244 100       3744 $valid = 0 if not assert_uri({ %$state, _keyword_path_suffix => $uri }, undef, $uri);
380             }
381              
382             # we cannot return an error here for invalid or incomplete vocabulary lists, because
383             # - the specification vocabulary schemas themselves don't list Core,
384             # - it is possible for a metaschema to $ref to another metaschema that uses an unrecognized
385             # vocabulary uri while still validating those vocabulary keywords (e.g.
386             # https://spec.openapis.org/oas/3.1/schema-base/2021-05-20)
387             # Instead, we will verify these constraints when we actually use the metaschema, in
388             # _traverse_keyword_schema -> _fetch_vocabulary_data
389              
390 101         667 return $valid;
391             }
392              
393             # we do nothing with $vocabulary yet at evaluation time. When we know we are in a metaschema,
394             # we can scan the URIs included here and either abort if a vocabulary is enabled that we do not
395             # understand, or turn on and off certain keyword behaviours based on the boolean values seen.
396              
397 650     650   2028 sub _traverse_keyword_comment ($class, $schema, $state) {
  650         1619  
  650         1782  
  650         1607  
  650         1497  
398 650 50       2984 return if not assert_keyword_type($state, $schema, 'string');
399 650         2588 return 1;
400             }
401              
402             # we do nothing with $comment at evaluation time, including not collecting its value for annotations.
403              
404 593     593   4570 sub _traverse_keyword_definitions { shift->traverse_object_schemas(@_) }
405 1124     1124   7991 sub _traverse_keyword_defs { shift->traverse_object_schemas(@_) }
406              
407             # we do nothing directly with $defs at evaluation time, including not collecting its value for
408             # annotations.
409              
410             1;
411              
412             __END__