| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
50
|
|
|
50
|
|
19458308
|
use strict; |
|
|
50
|
|
|
|
|
131
|
|
|
|
50
|
|
|
|
|
2254
|
|
|
2
|
50
|
|
|
50
|
|
324
|
use warnings; |
|
|
50
|
|
|
|
|
111
|
|
|
|
50
|
|
|
|
|
5338
|
|
|
3
|
|
|
|
|
|
|
package JSON::Schema::Modern; # git description: v0.631-7-g486a9f1e |
|
4
|
|
|
|
|
|
|
# vim: set ts=8 sts=2 sw=2 tw=100 et : |
|
5
|
|
|
|
|
|
|
# ABSTRACT: Validate data against a schema using a JSON Schema |
|
6
|
|
|
|
|
|
|
# KEYWORDS: JSON Schema validator data validation structure specification |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '0.632'; |
|
9
|
|
|
|
|
|
|
|
|
10
|
50
|
|
|
50
|
|
1121
|
use 5.020; # for fc, unicode_strings features |
|
|
50
|
|
|
|
|
227
|
|
|
11
|
50
|
|
|
50
|
|
30234
|
use Moo; |
|
|
50
|
|
|
|
|
427472
|
|
|
|
50
|
|
|
|
|
322
|
|
|
12
|
50
|
|
|
50
|
|
84519
|
use strictures 2; |
|
|
50
|
|
|
|
|
503
|
|
|
|
50
|
|
|
|
|
2308
|
|
|
13
|
50
|
|
|
50
|
|
24461
|
use stable 0.031 'postderef'; |
|
|
50
|
|
|
|
|
991
|
|
|
|
50
|
|
|
|
|
422
|
|
|
14
|
50
|
|
|
50
|
|
11033
|
use experimental 'signatures'; |
|
|
50
|
|
|
|
|
159
|
|
|
|
50
|
|
|
|
|
268
|
|
|
15
|
50
|
|
|
50
|
|
3204
|
no autovivification warn => qw(fetch store exists delete); |
|
|
50
|
|
|
|
|
176
|
|
|
|
50
|
|
|
|
|
523
|
|
|
16
|
50
|
|
|
49
|
|
4585
|
use if "$]" >= 5.022, experimental => 're_strict'; |
|
|
49
|
|
|
|
|
139
|
|
|
|
49
|
|
|
|
|
1692
|
|
|
17
|
49
|
|
|
49
|
|
5204
|
no if "$]" >= 5.031009, feature => 'indirect'; |
|
|
49
|
|
|
|
|
125
|
|
|
|
49
|
|
|
|
|
3934
|
|
|
18
|
49
|
|
|
49
|
|
421
|
no if "$]" >= 5.033001, feature => 'multidimensional'; |
|
|
49
|
|
|
|
|
106
|
|
|
|
49
|
|
|
|
|
3273
|
|
|
19
|
49
|
|
|
49
|
|
423
|
no if "$]" >= 5.033006, feature => 'bareword_filehandles'; |
|
|
49
|
|
|
|
|
168
|
|
|
|
49
|
|
|
|
|
3380
|
|
|
20
|
49
|
|
|
49
|
|
403
|
no if "$]" >= 5.041009, feature => 'smartmatch'; |
|
|
49
|
|
|
|
|
140
|
|
|
|
49
|
|
|
|
|
2574
|
|
|
21
|
49
|
|
|
49
|
|
568
|
no feature 'switch'; |
|
|
49
|
|
|
|
|
232
|
|
|
|
49
|
|
|
|
|
1770
|
|
|
22
|
49
|
|
|
49
|
|
28754
|
use Mojo::JSON (); # for JSON_XS, MOJO_NO_JSON_XS environment variables |
|
|
49
|
|
|
|
|
11087410
|
|
|
|
49
|
|
|
|
|
2574
|
|
|
23
|
49
|
|
|
49
|
|
530
|
use Carp qw(croak carp); |
|
|
49
|
|
|
|
|
380
|
|
|
|
49
|
|
|
|
|
4327
|
|
|
24
|
49
|
|
|
48
|
|
509
|
use List::Util 1.55 qw(pairs first uniqint pairmap uniq min); |
|
|
48
|
|
|
|
|
1582
|
|
|
|
47
|
|
|
|
|
5722
|
|
|
25
|
47
|
|
|
48
|
|
360
|
use if "$]" < 5.041010, 'List::Util' => 'any'; |
|
|
48
|
|
|
|
|
673
|
|
|
|
48
|
|
|
|
|
2849
|
|
|
26
|
48
|
|
|
48
|
|
305
|
use if "$]" >= 5.041010, experimental => 'keyword_any'; |
|
|
48
|
|
|
|
|
492
|
|
|
|
47
|
|
|
|
|
1214
|
|
|
27
|
47
|
|
|
48
|
|
32575
|
use builtin::compat qw(refaddr load_module); |
|
|
48
|
|
|
|
|
981313
|
|
|
|
48
|
|
|
|
|
4334
|
|
|
28
|
48
|
|
|
48
|
|
35949
|
use Mojo::URL; |
|
|
48
|
|
|
|
|
541914
|
|
|
|
48
|
|
|
|
|
5159
|
|
|
29
|
48
|
|
|
48
|
|
31332
|
use Safe::Isa; |
|
|
48
|
|
|
|
|
34444
|
|
|
|
48
|
|
|
|
|
16579
|
|
|
30
|
48
|
|
|
48
|
|
25769
|
use Mojo::File 'path'; |
|
|
48
|
|
|
|
|
993676
|
|
|
|
48
|
|
|
|
|
8086
|
|
|
31
|
48
|
|
|
47
|
|
499
|
use Storable 'dclone'; |
|
|
47
|
|
|
|
|
115
|
|
|
|
47
|
|
|
|
|
3980
|
|
|
32
|
47
|
|
|
47
|
|
339
|
use File::ShareDir 'dist_dir'; |
|
|
47
|
|
|
|
|
99
|
|
|
|
47
|
|
|
|
|
3530
|
|
|
33
|
47
|
|
|
47
|
|
26013
|
use MooX::TypeTiny 0.002002; |
|
|
47
|
|
|
|
|
25998
|
|
|
|
47
|
|
|
|
|
278
|
|
|
34
|
47
|
|
|
47
|
|
424919
|
use Types::Standard 1.016003 qw(Bool Int Str HasMethods Enum InstanceOf HashRef Dict CodeRef Optional Slurpy ArrayRef Undef ClassName Tuple Map); |
|
|
47
|
|
|
|
|
6364794
|
|
|
|
47
|
|
|
|
|
744
|
|
|
35
|
47
|
|
|
47
|
|
251587
|
use Digest::MD5 'md5'; |
|
|
47
|
|
|
|
|
125
|
|
|
|
47
|
|
|
|
|
3630
|
|
|
36
|
47
|
|
|
47
|
|
28696
|
use Feature::Compat::Try; |
|
|
47
|
|
|
|
|
20034
|
|
|
|
47
|
|
|
|
|
384
|
|
|
37
|
47
|
|
|
47
|
|
28413
|
use JSON::Schema::Modern::Error; |
|
|
47
|
|
|
|
|
1179
|
|
|
|
47
|
|
|
|
|
2356
|
|
|
38
|
47
|
|
|
47
|
|
31071
|
use JSON::Schema::Modern::Result; |
|
|
47
|
|
|
|
|
703
|
|
|
|
47
|
|
|
|
|
2403
|
|
|
39
|
47
|
|
|
47
|
|
30367
|
use JSON::Schema::Modern::Document; |
|
|
47
|
|
|
|
|
1210
|
|
|
|
47
|
|
|
|
|
1006
|
|
|
40
|
47
|
|
|
47
|
|
3946
|
use JSON::Schema::Modern::Utilities qw(get_type canonical_uri E abort annotate_self jsonp is_type assert_uri local_annotations is_schema json_pointer_type canonical_uri_type load_cached_document); |
|
|
47
|
|
|
|
|
126
|
|
|
|
47
|
|
|
|
|
6313
|
|
|
41
|
47
|
|
|
47
|
|
463
|
use namespace::clean; |
|
|
47
|
|
|
|
|
145
|
|
|
|
47
|
|
|
|
|
570
|
|
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
our @CARP_NOT = qw( |
|
44
|
|
|
|
|
|
|
JSON::Schema::Modern::Document |
|
45
|
|
|
|
|
|
|
JSON::Schema::Modern::Vocabulary |
|
46
|
|
|
|
|
|
|
JSON::Schema::Modern::Vocabulary::Applicator |
|
47
|
|
|
|
|
|
|
JSON::Schema::Modern::Document::OpenAPI |
|
48
|
|
|
|
|
|
|
OpenAPI::Modern |
|
49
|
|
|
|
|
|
|
); |
|
50
|
|
|
|
|
|
|
|
|
51
|
47
|
|
|
47
|
|
40051
|
use constant SPECIFICATION_VERSION_DEFAULT => 'draft2020-12'; |
|
|
47
|
|
|
|
|
122
|
|
|
|
47
|
|
|
|
|
4314
|
|
|
52
|
47
|
|
|
47
|
|
318
|
use constant SPECIFICATION_VERSIONS_SUPPORTED => [qw(draft4 draft6 draft7 draft2019-09 draft2020-12)]; |
|
|
47
|
|
|
|
|
120
|
|
|
|
47
|
|
|
|
|
45706
|
|
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
has specification_version => ( |
|
55
|
|
|
|
|
|
|
is => 'ro', |
|
56
|
|
|
|
|
|
|
isa => Enum(SPECIFICATION_VERSIONS_SUPPORTED), |
|
57
|
|
|
|
|
|
|
coerce => sub { |
|
58
|
|
|
|
|
|
|
return $_[0] if any { $_[0] eq $_ } SPECIFICATION_VERSIONS_SUPPORTED->@*; |
|
59
|
|
|
|
|
|
|
my $real = 'draft'.($_[0]//''); |
|
60
|
|
|
|
|
|
|
(any { $real eq $_ } SPECIFICATION_VERSIONS_SUPPORTED->@*) ? $real : $_[0]; |
|
61
|
|
|
|
|
|
|
}, |
|
62
|
|
|
|
|
|
|
); |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
has output_format => ( |
|
65
|
|
|
|
|
|
|
is => 'ro', |
|
66
|
|
|
|
|
|
|
isa => Enum(JSON::Schema::Modern::Result->OUTPUT_FORMATS), |
|
67
|
|
|
|
|
|
|
default => 'basic', |
|
68
|
|
|
|
|
|
|
); |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
has short_circuit => ( |
|
71
|
|
|
|
|
|
|
is => 'ro', |
|
72
|
|
|
|
|
|
|
isa => Bool, |
|
73
|
|
|
|
|
|
|
lazy => 1, |
|
74
|
|
|
|
|
|
|
default => sub { $_[0]->output_format eq 'flag' && !$_[0]->collect_annotations }, |
|
75
|
|
|
|
|
|
|
); |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
has max_traversal_depth => ( |
|
78
|
|
|
|
|
|
|
is => 'ro', |
|
79
|
|
|
|
|
|
|
isa => Int, |
|
80
|
|
|
|
|
|
|
default => 50, |
|
81
|
|
|
|
|
|
|
); |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
has validate_formats => ( |
|
84
|
|
|
|
|
|
|
is => 'ro', |
|
85
|
|
|
|
|
|
|
isa => Bool, |
|
86
|
|
|
|
|
|
|
lazy => 1, |
|
87
|
|
|
|
|
|
|
# as specified by https://json-schema.org/draft//schema#/$vocabulary |
|
88
|
|
|
|
|
|
|
default => sub { ($_[0]->specification_version//SPECIFICATION_VERSION_DEFAULT) =~ /^draft[467]\z/ ? 1 : 0 }, |
|
89
|
|
|
|
|
|
|
); |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
has validate_content_schemas => ( |
|
92
|
|
|
|
|
|
|
is => 'ro', |
|
93
|
|
|
|
|
|
|
isa => Bool, |
|
94
|
|
|
|
|
|
|
lazy => 1, |
|
95
|
|
|
|
|
|
|
# defaults to false in latest versions, as specified by |
|
96
|
|
|
|
|
|
|
# https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.8.2 |
|
97
|
|
|
|
|
|
|
default => sub { ($_[0]->specification_version//'') eq 'draft7' }, |
|
98
|
|
|
|
|
|
|
); |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
has [qw(collect_annotations scalarref_booleans stringy_numbers strict with_defaults)] => ( |
|
101
|
|
|
|
|
|
|
is => 'ro', |
|
102
|
|
|
|
|
|
|
isa => Bool, |
|
103
|
|
|
|
|
|
|
); |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Validation §7.1-2: "Note that the "type" keyword in this specification defines an "integer" type |
|
106
|
|
|
|
|
|
|
# which is not part of the data model. Therefore a format attribute can be limited to numbers, but |
|
107
|
|
|
|
|
|
|
# not specifically to integers." |
|
108
|
|
|
|
|
|
|
my $core_types = Enum[qw(null object array boolean string number)]; |
|
109
|
|
|
|
|
|
|
my @core_formats = qw(date-time date time duration email idn-email hostname idn-hostname ipv4 ipv6 uri uri-reference iri iri-reference uuid uri-template json-pointer relative-json-pointer regex); |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# { $format_name => { type => ..., sub => ... }, ... } |
|
112
|
|
|
|
|
|
|
has _format_validations => ( |
|
113
|
|
|
|
|
|
|
is => 'bare', |
|
114
|
|
|
|
|
|
|
isa => my $format_type = HashRef[Dict[ |
|
115
|
|
|
|
|
|
|
type => $core_types|ArrayRef[$core_types], |
|
116
|
|
|
|
|
|
|
sub => CodeRef, |
|
117
|
|
|
|
|
|
|
]], |
|
118
|
|
|
|
|
|
|
init_arg => 'format_validations', |
|
119
|
|
|
|
|
|
|
); |
|
120
|
|
|
|
|
|
|
|
|
121
|
957
|
|
100
|
957
|
|
1764
|
sub _get_format_validation ($self, $format) { ($self->{_format_validations}//{})->{$format} } |
|
|
957
|
|
|
|
|
1857
|
|
|
|
957
|
|
|
|
|
1806
|
|
|
|
957
|
|
|
|
|
1519
|
|
|
|
957
|
|
|
|
|
12071
|
|
|
122
|
|
|
|
|
|
|
|
|
123
|
12
|
|
|
12
|
1
|
36457
|
sub add_format_validation ($self, $format, $definition) { |
|
|
12
|
|
|
|
|
31
|
|
|
|
12
|
|
|
|
|
22
|
|
|
|
12
|
|
|
|
|
77
|
|
|
|
12
|
|
|
|
|
25
|
|
|
124
|
12
|
50
|
100
|
|
|
95
|
return if exists(($self->{_format_validations}//{})->{$format}); |
|
125
|
|
|
|
|
|
|
|
|
126
|
12
|
100
|
|
|
|
76
|
$definition = { type => 'string', sub => $definition } if ref $definition ne 'HASH'; |
|
127
|
12
|
|
|
|
|
119
|
$format_type->({ $format => $definition }); |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# all core formats are of type string (so far); changing type of custom format is permitted |
|
130
|
|
|
|
|
|
|
croak "Type for override of format $format does not match original type" |
|
131
|
5
|
100
|
100
|
|
|
404
|
if any { $format eq $_ } @core_formats and $definition->{type} ne 'string'; |
|
|
66
|
|
|
|
|
709
|
|
|
132
|
|
|
|
|
|
|
|
|
133
|
47
|
|
|
47
|
|
479
|
use autovivification 'store'; |
|
|
47
|
|
|
|
|
119
|
|
|
|
47
|
|
|
|
|
506
|
|
|
134
|
4
|
|
|
|
|
20
|
$self->{_format_validations}{$format} = $definition; |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
around BUILDARGS => sub ($orig, $class, @args) { |
|
138
|
|
|
|
|
|
|
my $args = $class->$orig(@args); |
|
139
|
|
|
|
|
|
|
croak 'output_format: strict_basic can only be used with specification_version: draft2019-09' |
|
140
|
|
|
|
|
|
|
if ($args->{output_format}//'') eq 'strict_basic' |
|
141
|
|
|
|
|
|
|
and ($args->{specification_version}//'') ne 'draft2019-09'; |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
croak 'collect_annotations cannot be used with specification_version '.$args->{specification_version} |
|
144
|
|
|
|
|
|
|
if $args->{collect_annotations} and ($args->{specification_version}//'') =~ /^draft[467]\z/; |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
$args->{format_validations} = +{ |
|
147
|
|
|
|
|
|
|
map +($_->[0] => ref $_->[1] eq 'HASH' ? $_->[1] : +{ type => 'string', sub => $_->[1] }), |
|
148
|
|
|
|
|
|
|
pairs $args->{format_validations}->%* |
|
149
|
|
|
|
|
|
|
} if $args->{format_validations}; |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
return $args; |
|
152
|
|
|
|
|
|
|
}; |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub add_schema { |
|
155
|
17381
|
50
|
|
17381
|
1
|
206858
|
croak 'insufficient arguments' if @_ < 2; |
|
156
|
17381
|
|
|
|
|
35462
|
my $self = shift; |
|
157
|
|
|
|
|
|
|
|
|
158
|
17381
|
100
|
|
|
|
89970
|
if ($_[0]->$_isa('JSON::Schema::Modern::Document')) { |
|
159
|
2
|
|
|
|
|
288
|
Carp::carp('use of deprecated form of add_schema with document'); |
|
160
|
2
|
|
|
|
|
26
|
return $self->add_document($_[0]); |
|
161
|
|
|
|
|
|
|
} |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# TODO: resolve $uri against $self->base_uri |
|
164
|
17380
|
50
|
|
|
|
247656
|
my $uri = !is_schema($_[0]) ? Mojo::URL->new(shift) |
|
|
|
100
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
: $_[0]->$_isa('Mojo::URL') ? shift : Mojo::URL->new; |
|
166
|
|
|
|
|
|
|
|
|
167
|
17380
|
100
|
|
|
|
335601
|
croak 'cannot add a schema with a uri with a fragment' if defined $uri->fragment; |
|
168
|
17379
|
50
|
|
|
|
127742
|
croak 'insufficient arguments' if not @_; |
|
169
|
|
|
|
|
|
|
|
|
170
|
17379
|
100
|
|
|
|
53311
|
if ($_[0]->$_isa('JSON::Schema::Modern::Document')) { |
|
171
|
2
|
|
|
|
|
405
|
Carp::carp('use of deprecated form of add_schema with document'); |
|
172
|
2
|
|
|
|
|
18
|
return $self->add_document($uri, $_[0]); |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# document BUILD will trigger $self->traverse($schema) |
|
176
|
|
|
|
|
|
|
# Note we do not pass the uri to the document constructor, so resources in that document may still |
|
177
|
|
|
|
|
|
|
# be relative |
|
178
|
17378
|
|
|
|
|
698991
|
my $document = JSON::Schema::Modern::Document->new( |
|
179
|
|
|
|
|
|
|
schema => $_[0], |
|
180
|
|
|
|
|
|
|
evaluator => $self, # used mainly for traversal during document construction |
|
181
|
|
|
|
|
|
|
); |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# try to reuse the same document, if the same schema is being added twice: |
|
184
|
|
|
|
|
|
|
# this results in _add_resource silently ignoring the duplicate add, rather than erroring. |
|
185
|
17378
|
|
|
|
|
475596
|
my $schema_checksum = $document->_checksum(md5($self->_json_decoder->encode($document->schema))); |
|
186
|
17378
|
100
|
|
|
|
1305210
|
if (my $existing_doc = first { |
|
187
|
722237
|
|
66
|
722237
|
|
12579728
|
my $existing_checksum = $_->_checksum |
|
188
|
|
|
|
|
|
|
// $_->_checksum(md5($self->_json_decoder->encode($_->schema))); |
|
189
|
722237
|
100
|
|
|
|
5135448
|
$existing_checksum eq $schema_checksum |
|
190
|
|
|
|
|
|
|
and $_->canonical_uri eq $document->canonical_uri |
|
191
|
|
|
|
|
|
|
# FIXME: must also check spec version/metaschema_uri/vocabularies |
|
192
|
|
|
|
|
|
|
} uniqint map $_->{document}, $self->_canonical_resources) { |
|
193
|
12293
|
|
|
|
|
3860413
|
$document = $existing_doc; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
17378
|
|
|
|
|
694025
|
$self->add_document($uri, $document); |
|
197
|
|
|
|
|
|
|
} |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub add_document { |
|
200
|
18190
|
50
|
|
18190
|
1
|
164234
|
croak 'insufficient arguments' if @_ < 2; |
|
201
|
18190
|
|
|
|
|
48257
|
my $self = shift; |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# TODO: resolve $uri against $self->base_uri |
|
204
|
18190
|
50
|
|
|
|
92712
|
my $base_uri = !$_[0]->$_isa('JSON::Schema::Modern::Document') ? Mojo::URL->new(shift) |
|
|
|
100
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
: $_[0]->$_isa('Mojo::URL') ? shift : Mojo::URL->new; |
|
206
|
|
|
|
|
|
|
|
|
207
|
18190
|
50
|
|
|
|
7384227
|
croak 'cannot add a schema with a uri with a fragment' if defined $base_uri->fragment; |
|
208
|
18190
|
50
|
|
|
|
140017
|
croak 'insufficient arguments' if not @_; |
|
209
|
|
|
|
|
|
|
|
|
210
|
18190
|
|
|
|
|
43213
|
my $document = shift; |
|
211
|
18190
|
50
|
|
|
|
67906
|
croak 'wrong document type' if not $document->$_isa('JSON::Schema::Modern::Document'); |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# we will never add a document to the resource index if it has errors |
|
214
|
18190
|
100
|
|
|
|
320975
|
die JSON::Schema::Modern::Result->new( |
|
215
|
|
|
|
|
|
|
output_format => $self->output_format, |
|
216
|
|
|
|
|
|
|
valid => 0, |
|
217
|
|
|
|
|
|
|
errors => [ $document->errors ], |
|
218
|
|
|
|
|
|
|
exception => 1, |
|
219
|
|
|
|
|
|
|
) if $document->has_errors; |
|
220
|
|
|
|
|
|
|
|
|
221
|
18010
|
100
|
|
|
|
81053
|
if (not length $base_uri){ |
|
222
|
17177
|
|
|
|
|
2416284
|
foreach my $res_pair ($document->resource_pairs) { |
|
223
|
18037
|
|
|
|
|
317770
|
my ($uri_string, $doc_resource) = @$res_pair; |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# this might croak if there are duplicates or malformed entries. |
|
226
|
18037
|
|
|
|
|
200854
|
$self->_add_resource($uri_string => +{ $doc_resource->%*, document => $document }); |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
17172
|
|
|
|
|
8928287
|
return $document; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
834
|
|
|
|
|
180665
|
my @root; # uri_string => resource hash of the resource at path '' |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# document resources are added after resolving each resource against our provided base uri |
|
235
|
834
|
|
|
|
|
3891
|
foreach my $res_pair ($document->resource_pairs) { |
|
236
|
864
|
|
|
|
|
2414
|
my ($uri_string, $doc_resource) = @$res_pair; |
|
237
|
864
|
|
|
|
|
3128
|
$uri_string = Mojo::URL->new($uri_string)->to_abs($base_uri)->to_string; |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
my $new_resource = { |
|
240
|
|
|
|
|
|
|
canonical_uri => Mojo::URL->new($doc_resource->{canonical_uri})->to_abs($base_uri), |
|
241
|
864
|
|
|
|
|
559469
|
$doc_resource->%{qw(path specification_version vocabularies)}, |
|
242
|
|
|
|
|
|
|
document => $document, |
|
243
|
|
|
|
|
|
|
}; |
|
244
|
|
|
|
|
|
|
|
|
245
|
864
|
|
100
|
|
|
465738
|
foreach my $anchor (keys (($doc_resource->{anchors}//{})->%*)) { |
|
246
|
47
|
|
|
47
|
|
78760
|
use autovivification 'store'; |
|
|
47
|
|
|
|
|
118
|
|
|
|
47
|
|
|
|
|
297
|
|
|
247
|
|
|
|
|
|
|
$new_resource->{anchors}{$anchor} = { |
|
248
|
|
|
|
|
|
|
$doc_resource->{anchors}{$anchor}->%{path}, |
|
249
|
|
|
|
|
|
|
(map +($_->[1] ? @$_ : ()), [ $doc_resource->{anchors}{$anchor}->%{dynamic} ]), |
|
250
|
170
|
100
|
|
|
|
2352
|
canonical_uri => Mojo::URL->new($doc_resource->{anchors}{$anchor}{canonical_uri})->to_abs($base_uri), |
|
251
|
|
|
|
|
|
|
}; |
|
252
|
|
|
|
|
|
|
} |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# this might croak if there are duplicates or malformed entries. |
|
255
|
864
|
|
|
|
|
86580
|
$self->_add_resource($uri_string => $new_resource); |
|
256
|
857
|
100
|
66
|
|
|
461018
|
@root = ($uri_string => $new_resource) if $new_resource->{path} eq '' and $uri_string !~ /#./; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# associate the root resource with the base uri we were provided, if it does not already exist |
|
260
|
827
|
100
|
|
|
|
4822
|
$self->_add_resource($base_uri.'' => $root[1]) if $root[0] ne $base_uri; |
|
261
|
|
|
|
|
|
|
|
|
262
|
827
|
|
|
|
|
200347
|
return $document; |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
4
|
|
|
4
|
1
|
14900
|
sub evaluate_json_string ($self, $json_data, $schema, $config_override = {}) { |
|
|
4
|
|
|
|
|
25
|
|
|
|
4
|
|
|
|
|
13
|
|
|
|
4
|
|
|
|
|
12
|
|
|
|
4
|
|
|
|
|
56
|
|
|
|
4
|
|
|
|
|
14
|
|
|
266
|
4
|
50
|
|
|
|
140
|
croak 'evaluate_json_string called in void context' if not defined wantarray; |
|
267
|
|
|
|
|
|
|
|
|
268
|
4
|
|
|
|
|
23
|
my $data; |
|
269
|
4
|
|
|
|
|
13
|
try { |
|
270
|
4
|
|
|
|
|
141
|
$data = $self->_json_decoder->decode($json_data) |
|
271
|
|
|
|
|
|
|
} |
|
272
|
|
|
|
|
|
|
catch ($e) { |
|
273
|
3
|
|
|
|
|
147
|
return JSON::Schema::Modern::Result->new( |
|
274
|
|
|
|
|
|
|
output_format => $self->output_format, |
|
275
|
|
|
|
|
|
|
valid => 0, |
|
276
|
|
|
|
|
|
|
exception => 1, |
|
277
|
|
|
|
|
|
|
errors => [ |
|
278
|
|
|
|
|
|
|
JSON::Schema::Modern::Error->new( |
|
279
|
|
|
|
|
|
|
depth => 0, |
|
280
|
|
|
|
|
|
|
mode => 'traverse', |
|
281
|
|
|
|
|
|
|
keyword => undef, |
|
282
|
|
|
|
|
|
|
keyword_location => '', |
|
283
|
|
|
|
|
|
|
error => $e, |
|
284
|
|
|
|
|
|
|
) |
|
285
|
|
|
|
|
|
|
], |
|
286
|
|
|
|
|
|
|
); |
|
287
|
|
|
|
|
|
|
} |
|
288
|
|
|
|
|
|
|
|
|
289
|
2
|
|
|
|
|
30
|
return $self->evaluate($data, $schema, $config_override); |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# this is called whenever we need to walk a document for something. |
|
293
|
|
|
|
|
|
|
# for now it is just called when a ::Document object is created, to verify the integrity of the |
|
294
|
|
|
|
|
|
|
# schema structure, to identify the metaschema (via the $schema keyword), and to extract all |
|
295
|
|
|
|
|
|
|
# embedded resources via $id and $anchor keywords within. |
|
296
|
|
|
|
|
|
|
# Returns the internal $state object accumulated during the traversal. |
|
297
|
17930
|
|
|
17930
|
1
|
107070
|
sub traverse ($self, $schema_reference, $config_override = {}) { |
|
|
17930
|
|
|
|
|
35067
|
|
|
|
17930
|
|
|
|
|
35814
|
|
|
|
17930
|
|
|
|
|
37237
|
|
|
|
17930
|
|
|
|
|
30877
|
|
|
298
|
17930
|
|
|
|
|
72889
|
my %overrides = %$config_override; |
|
299
|
17930
|
|
|
|
|
89550
|
delete @overrides{qw(callbacks initial_schema_uri metaschema_uri traversed_keyword_path specification_version skip_ref_checks)}; |
|
300
|
17930
|
50
|
|
|
|
63853
|
croak join(', ', sort keys %overrides), ' not supported as a config override in traverse' |
|
301
|
|
|
|
|
|
|
if keys %overrides; |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Note: the starting position is not guaranteed to be at the root of the $document, |
|
304
|
|
|
|
|
|
|
# nor is the fragment portion of this uri necessarily empty |
|
305
|
17930
|
|
66
|
|
|
120288
|
my $initial_uri = Mojo::URL->new($config_override->{initial_schema_uri} // ()); |
|
306
|
17930
|
|
100
|
|
|
7567806
|
my $initial_path = $config_override->{traversed_keyword_path} // ''; |
|
307
|
17930
|
|
100
|
|
|
168431
|
my $spec_version = $config_override->{specification_version} // $self->specification_version // SPECIFICATION_VERSION_DEFAULT; |
|
|
|
|
100
|
|
|
|
|
|
308
|
|
|
|
|
|
|
|
|
309
|
17930
|
50
|
|
|
|
110168
|
croak 'traversed_keyword_path must be a json pointer' if $initial_path !~ m{^(?:/|\z)}; |
|
310
|
|
|
|
|
|
|
|
|
311
|
17930
|
100
|
|
|
|
67321
|
if (length(my $uri_path = $initial_uri->fragment)) { |
|
312
|
5
|
50
|
|
|
|
57
|
croak 'initial_schema_uri fragment must be a json pointer' if $uri_path !~ m{^/}; |
|
313
|
|
|
|
|
|
|
|
|
314
|
5
|
50
|
|
|
|
30
|
croak 'traversed_keyword_path does not match initial_schema_uri path fragment' |
|
315
|
|
|
|
|
|
|
if substr($initial_path, -length($uri_path)) ne $uri_path; |
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
my $state = { |
|
319
|
|
|
|
|
|
|
depth => 0, |
|
320
|
|
|
|
|
|
|
data_path => '', # this never changes since we don't have an instance yet |
|
321
|
|
|
|
|
|
|
initial_schema_uri => $initial_uri, # the canonical URI as of the start of this method or last $id |
|
322
|
|
|
|
|
|
|
traversed_keyword_path => $initial_path, # the accumulated traversal path as of the start or last $id |
|
323
|
|
|
|
|
|
|
keyword_path => '', # the rest of the path, since the start of this method or last $id |
|
324
|
|
|
|
|
|
|
specification_version => $spec_version, |
|
325
|
|
|
|
|
|
|
errors => [], |
|
326
|
|
|
|
|
|
|
identifiers => {}, |
|
327
|
|
|
|
|
|
|
subschemas => [], |
|
328
|
|
|
|
|
|
|
$config_override->{skip_ref_checks} ? () : (references => []), |
|
329
|
|
|
|
|
|
|
callbacks => $config_override->{callbacks} // {}, |
|
330
|
17930
|
100
|
100
|
|
|
455866
|
evaluator => $self, |
|
331
|
|
|
|
|
|
|
traverse => 1, |
|
332
|
|
|
|
|
|
|
}; |
|
333
|
|
|
|
|
|
|
|
|
334
|
17930
|
|
|
|
|
55017
|
my $valid = 1; |
|
335
|
|
|
|
|
|
|
|
|
336
|
17930
|
|
|
|
|
53837
|
try { |
|
337
|
|
|
|
|
|
|
# determine the initial value of specification_version and vocabularies, so we have something to start |
|
338
|
|
|
|
|
|
|
# with in _traverse_subschema(). |
|
339
|
|
|
|
|
|
|
# a subsequent "$schema" keyword can still change these values, and it is always processed |
|
340
|
|
|
|
|
|
|
# first, so the override is skipped if the keyword exists in the schema |
|
341
|
|
|
|
|
|
|
$state->{metaschema_uri} = |
|
342
|
|
|
|
|
|
|
(ref $schema_reference eq 'HASH' && exists $schema_reference->{'$schema'} ? undef |
|
343
|
17930
|
100
|
100
|
|
|
357134
|
: $config_override->{metaschema_uri}) // $self->METASCHEMA_URIS->{$spec_version}; |
|
|
|
|
66
|
|
|
|
|
|
344
|
|
|
|
|
|
|
|
|
345
|
17930
|
100
|
|
|
|
95495
|
if (my $metaschema_info = $self->_get_metaschema_vocabulary_classes($state->{metaschema_uri})) { |
|
346
|
17924
|
|
|
|
|
634450
|
$state->@{qw(specification_version vocabularies)} = @$metaschema_info; |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
else { |
|
349
|
|
|
|
|
|
|
# metaschema has not been processed for vocabularies yet... |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
die 'something went wrong - cannot get metaschema data for '.$state->{metaschema_uri} |
|
352
|
7
|
50
|
|
|
|
2173
|
if not $config_override->{metaschema_uri}; |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
# use the Core vocabulary to set metaschema info via the '$schema' keyword implementation |
|
355
|
|
|
|
|
|
|
$valid = $self->_get_metaschema_vocabulary_classes($self->METASCHEMA_URIS->{$spec_version})->[1][0] |
|
356
|
7
|
|
|
|
|
85
|
->_traverse_keyword_schema({ '$schema' => $state->{metaschema_uri}.'' }, $state); |
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
|
|
359
|
17930
|
100
|
66
|
|
|
203329
|
$valid = $self->_traverse_subschema($schema_reference, $state) if $valid and not $state->{errors}->@*; |
|
360
|
17930
|
50
|
66
|
|
|
61922
|
die 'result is false but there are no errors' if not $valid and not $state->{errors}->@*; |
|
361
|
17930
|
50
|
66
|
|
|
168176
|
die 'result is true but there are errors' if $valid and $state->{errors}->@*; |
|
362
|
|
|
|
|
|
|
} |
|
363
|
|
|
|
|
|
|
catch ($e) { |
|
364
|
1
|
0
|
|
|
|
5
|
if ($e->$_isa('JSON::Schema::Modern::Result')) { |
|
|
|
0
|
|
|
|
|
|
|
365
|
1
|
|
|
|
|
2
|
push $state->{errors}->@*, $e->errors; |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
elsif ($e->$_isa('JSON::Schema::Modern::Error')) { |
|
368
|
|
|
|
|
|
|
# note: we should never be here, since traversal subs are no longer fatal |
|
369
|
1
|
|
|
|
|
20
|
push $state->{errors}->@*, $e; |
|
370
|
|
|
|
|
|
|
} |
|
371
|
|
|
|
|
|
|
else { |
|
372
|
1
|
|
|
|
|
5
|
E({ %$state, exception => 1 }, 'EXCEPTION: '.$e); |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
} |
|
375
|
|
|
|
|
|
|
|
|
376
|
17930
|
|
|
|
|
106077
|
return $state; |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# the actual runtime evaluation of the schema against input data. |
|
380
|
17376
|
|
|
17376
|
1
|
33491033
|
sub evaluate ($self, $data, $schema_reference, $config_override = {}) { |
|
|
17376
|
|
|
|
|
45250
|
|
|
|
17376
|
|
|
|
|
41878
|
|
|
|
17376
|
|
|
|
|
36417
|
|
|
|
17376
|
|
|
|
|
47386
|
|
|
|
17376
|
|
|
|
|
39999
|
|
|
381
|
17376
|
50
|
|
|
|
74501
|
croak 'evaluate called in void context' if not defined wantarray; |
|
382
|
|
|
|
|
|
|
|
|
383
|
17376
|
|
|
|
|
60934
|
my %overrides = %$config_override; |
|
384
|
17376
|
|
|
|
|
84355
|
delete @overrides{qw(validate_formats validate_content_schemas short_circuit collect_annotations scalarref_booleans stringy_numbers strict with_defaults callbacks data_path traversed_keyword_path _strict_schema_data)}; |
|
385
|
17376
|
50
|
|
|
|
68128
|
croak join(', ', sort keys %overrides), ' not supported as a config override in evaluate' |
|
386
|
|
|
|
|
|
|
if keys %overrides; |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
my $state = { |
|
389
|
|
|
|
|
|
|
data_path => $config_override->{data_path} // '', |
|
390
|
17375
|
|
100
|
|
|
244635
|
traversed_keyword_path => $config_override->{traversed_keyword_path} // '', # the accumulated path as of the start of evaluation or last $id or $ref |
|
|
|
|
100
|
|
|
|
|
|
391
|
|
|
|
|
|
|
initial_schema_uri => Mojo::URL->new, # the canonical URI as of the start of evaluation or last $id or $ref |
|
392
|
|
|
|
|
|
|
keyword_path => '', # the rest of the path, since the start of evaluation or last $id or $ref |
|
393
|
|
|
|
|
|
|
errors => [], |
|
394
|
|
|
|
|
|
|
depth => 0, |
|
395
|
|
|
|
|
|
|
}; |
|
396
|
|
|
|
|
|
|
|
|
397
|
17375
|
|
|
|
|
347954
|
my $valid; |
|
398
|
17375
|
|
|
|
|
44897
|
try { |
|
399
|
17375
|
100
|
100
|
|
|
88064
|
if (is_schema($schema_reference)) { |
|
|
|
100
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# traverse is called via add_schema -> ::Document->new -> ::Document->BUILD |
|
401
|
17301
|
|
|
|
|
79990
|
$schema_reference = $self->add_schema($schema_reference)->canonical_uri; |
|
402
|
|
|
|
|
|
|
} |
|
403
|
|
|
|
|
|
|
elsif (ref $schema_reference and not $schema_reference->$_isa('Mojo::URL')) { |
|
404
|
4
|
|
|
|
|
37
|
abort($state, 'invalid schema type: %s', get_type($schema_reference)); |
|
405
|
|
|
|
|
|
|
} |
|
406
|
|
|
|
|
|
|
|
|
407
|
17186
|
|
|
|
|
243531
|
my $schema_info = $self->_fetch_from_uri($schema_reference); |
|
408
|
17186
|
100
|
|
|
|
67990
|
abort($state, 'EXCEPTION: unable to find resource "%s"', $schema_reference) |
|
409
|
|
|
|
|
|
|
if not $schema_info; |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
abort($state, 'EXCEPTION: "%s" is not a schema', $schema_reference) |
|
412
|
17180
|
100
|
|
|
|
120785
|
if not $schema_info->{document}->get_entity_at_location($schema_info->{document_path}); |
|
413
|
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
$state = +{ |
|
415
|
|
|
|
|
|
|
%$state, |
|
416
|
|
|
|
|
|
|
initial_schema_uri => $schema_info->{canonical_uri}, # the canonical URI as of the start of evaluation, or last $id or $ref |
|
417
|
|
|
|
|
|
|
$schema_info->%{qw(document specification_version vocabularies)}, |
|
418
|
|
|
|
|
|
|
dynamic_scope => [ $schema_info->{canonical_uri}->clone->fragment(undef) ], |
|
419
|
|
|
|
|
|
|
annotations => [], |
|
420
|
|
|
|
|
|
|
seen => {}, |
|
421
|
|
|
|
|
|
|
callbacks => $config_override->{callbacks} // {}, |
|
422
|
|
|
|
|
|
|
evaluator => $self, |
|
423
|
|
|
|
|
|
|
(map { |
|
424
|
120253
|
|
100
|
|
|
3248058
|
my $val = $config_override->{$_} // $self->$_; |
|
425
|
120253
|
100
|
|
|
|
1164809
|
defined $val ? ($_ => $val) : () |
|
426
|
|
|
|
|
|
|
# note: this is a subset of the allowed overrides defined above |
|
427
|
|
|
|
|
|
|
} qw(validate_formats validate_content_schemas short_circuit collect_annotations scalarref_booleans stringy_numbers strict)), |
|
428
|
17179
|
100
|
100
|
|
|
189668
|
$config_override->{with_defaults} // $self->with_defaults ? (defaults => {}) : (), |
|
|
|
|
66
|
|
|
|
|
|
429
|
|
|
|
|
|
|
}; |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# this hash will be added to at each level of schema evaluation |
|
432
|
17179
|
100
|
|
|
|
109566
|
$state->{seen_data_properties} = {} if $config_override->{_strict_schema_data}; |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# we're going to set collect_annotations during evaluation when we see an unevaluated* keyword |
|
435
|
|
|
|
|
|
|
# (or for object data when the _strict_schema_data configuration is set), |
|
436
|
|
|
|
|
|
|
# but after we pass to a new data scope we'll clear it again.. unless we've got the config set |
|
437
|
|
|
|
|
|
|
# globally for the entire evaluation, so we store that value in a high bit. |
|
438
|
17179
|
|
100
|
|
|
101626
|
$state->{collect_annotations} = ($state->{collect_annotations}//0) << 8; |
|
439
|
|
|
|
|
|
|
|
|
440
|
17179
|
|
|
|
|
112521
|
$valid = $self->_eval_subschema($data, $schema_info->{schema}, $state); |
|
441
|
17156
|
50
|
66
|
|
|
92340
|
warn 'result is false but there are no errors' if not $valid and not $state->{errors}->@*; |
|
442
|
17156
|
50
|
66
|
|
|
165639
|
warn 'result is true but there are errors' if $valid and $state->{errors}->@*; |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
catch ($e) { |
|
445
|
219
|
100
|
|
|
|
1512
|
if ($e->$_isa('JSON::Schema::Modern::Result')) { |
|
|
|
100
|
|
|
|
|
|
|
446
|
180
|
|
|
|
|
4501
|
return $e; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
elsif ($e->$_isa('JSON::Schema::Modern::Error')) { |
|
449
|
34
|
|
|
|
|
915
|
push $state->{errors}->@*, $e; |
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
else { |
|
452
|
5
|
|
|
|
|
138
|
$valid = E({ %$state, exception => 1 }, 'EXCEPTION: '.$e); |
|
453
|
|
|
|
|
|
|
} |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
|
|
456
|
17195
|
100
|
|
|
|
77941
|
if ($state->{seen_data_properties}) { |
|
457
|
5
|
|
|
|
|
14
|
my %unknown_keywords; |
|
458
|
5
|
|
|
|
|
79
|
foreach my $property (sort grep !$state->{seen_data_properties}{$_}, |
|
459
|
|
|
|
|
|
|
keys $state->{seen_data_properties}->%*) { |
|
460
|
15
|
|
|
|
|
83
|
my ($parent, $keyword) = ($property =~ m{^(.*)/([^/]*)\z}); |
|
461
|
15
|
|
100
|
|
|
77
|
push(($unknown_keywords{$parent}//=[])->@*, $keyword); |
|
462
|
|
|
|
|
|
|
} |
|
463
|
|
|
|
|
|
|
|
|
464
|
5
|
|
|
|
|
28
|
foreach my $parent (sort keys %unknown_keywords) { |
|
465
|
|
|
|
|
|
|
$valid = E({ %$state, data_path => $parent }, |
|
466
|
|
|
|
|
|
|
'unknown keyword%s seen in schema: %s', $unknown_keywords{$parent}->@* > 1 ? 's' : '', |
|
467
|
8
|
100
|
|
|
|
183
|
join(', ', sort $unknown_keywords{$parent}->@*)); |
|
468
|
|
|
|
|
|
|
} |
|
469
|
|
|
|
|
|
|
} |
|
470
|
|
|
|
|
|
|
|
|
471
|
17195
|
50
|
50
|
|
|
119692
|
die 'evaluate validity inconsistent with error count' if $valid xor !$state->{errors}->@*; |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
return JSON::Schema::Modern::Result->new( |
|
474
|
|
|
|
|
|
|
output_format => $self->output_format, |
|
475
|
|
|
|
|
|
|
valid => $valid, |
|
476
|
|
|
|
|
|
|
$valid |
|
477
|
|
|
|
|
|
|
# strip annotations from result if user didn't explicitly ask for them |
|
478
|
|
|
|
|
|
|
? ($config_override->{collect_annotations} // $self->collect_annotations |
|
479
|
|
|
|
|
|
|
? (annotations => $state->{annotations}) : ()) |
|
480
|
|
|
|
|
|
|
: (errors => $state->{errors}), |
|
481
|
17195
|
100
|
100
|
|
|
825279
|
$state->{defaults} ? (defaults => $state->{defaults}) : (), |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
); |
|
483
|
|
|
|
|
|
|
} |
|
484
|
|
|
|
|
|
|
|
|
485
|
10
|
|
|
11
|
1
|
46153
|
sub validate_schema ($self, $schema, $config_override = {}) { |
|
|
10
|
|
|
|
|
24
|
|
|
|
10
|
|
|
|
|
20
|
|
|
|
10
|
|
|
|
|
27
|
|
|
|
10
|
|
|
|
|
19
|
|
|
486
|
10
|
50
|
|
|
|
46
|
croak 'validate_schema called in void context' if not defined wantarray; |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
my $metaschema_uri = ref $schema eq 'HASH' && $schema->{'$schema'} ? $schema->{'$schema'} |
|
489
|
10
|
100
|
66
|
|
|
143
|
: $self->METASCHEMA_URIS->{$self->specification_version // $self->SPECIFICATION_VERSION_DEFAULT}; |
|
|
|
|
33
|
|
|
|
|
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
my $result = $self->evaluate($schema, $metaschema_uri, |
|
492
|
10
|
100
|
100
|
|
|
125
|
{ %$config_override, $self->strict || $config_override->{strict} ? (_strict_schema_data => 1) : () }); |
|
493
|
|
|
|
|
|
|
|
|
494
|
10
|
100
|
|
|
|
288
|
return $result if not $result->valid; |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# the traversal pass will validate all constraints that weren't handled by the metaschema |
|
497
|
3
|
|
|
|
|
123
|
my $state = $self->traverse($schema); |
|
498
|
|
|
|
|
|
|
return JSON::Schema::Modern::Result->new( |
|
499
|
|
|
|
|
|
|
output_format => $self->output_format, |
|
500
|
|
|
|
|
|
|
valid => 0, |
|
501
|
|
|
|
|
|
|
errors => $state->{errors}, |
|
502
|
3
|
100
|
|
|
|
43
|
) if $state->{errors}->@*; |
|
503
|
|
|
|
|
|
|
|
|
504
|
2
|
|
|
|
|
47
|
return $result; # valid: true |
|
505
|
|
|
|
|
|
|
} |
|
506
|
|
|
|
|
|
|
|
|
507
|
8
|
|
|
9
|
1
|
38010
|
sub get ($self, $uri_reference) { |
|
|
8
|
|
|
|
|
19
|
|
|
|
8
|
|
|
|
|
15
|
|
|
|
8
|
|
|
|
|
18
|
|
|
508
|
8
|
100
|
|
|
|
26
|
if (wantarray) { |
|
509
|
5
|
|
|
|
|
22
|
my $schema_info = $self->_fetch_from_uri($uri_reference); |
|
510
|
5
|
100
|
|
|
|
27
|
return if not $schema_info; |
|
511
|
4
|
100
|
|
|
|
650
|
my $subschema = ref $schema_info->{schema} ? dclone($schema_info->{schema}) : $schema_info->{schema}; |
|
512
|
4
|
|
|
|
|
45
|
return ($subschema, $schema_info->{canonical_uri}); |
|
513
|
|
|
|
|
|
|
} |
|
514
|
|
|
|
|
|
|
else { # abridged version of _fetch_from_uri |
|
515
|
3
|
50
|
|
|
|
28
|
$uri_reference = Mojo::URL->new($uri_reference) if not ref $uri_reference; |
|
516
|
3
|
|
|
|
|
609
|
my $fragment = $uri_reference->fragment; |
|
517
|
3
|
|
|
|
|
21
|
my $resource = $self->_get_or_load_resource($uri_reference->clone->fragment(undef)); |
|
518
|
3
|
50
|
|
|
|
135
|
return if not $resource; |
|
519
|
|
|
|
|
|
|
|
|
520
|
3
|
|
|
|
|
6
|
my $schema; |
|
521
|
3
|
100
|
100
|
|
|
22
|
if (not length($fragment) or $fragment =~ m{^/}) { |
|
522
|
2
|
|
100
|
|
|
122
|
$schema = $resource->{document}->get($resource->{path}.($fragment//'')); |
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
else { # we are following a URI with a plain-name fragment |
|
525
|
1
|
50
|
50
|
|
|
24
|
return if not my $subresource = ($resource->{anchors}//{})->{$fragment}; |
|
526
|
0
|
|
|
|
|
0
|
$schema = $resource->{document}->get($subresource->{path}); |
|
527
|
|
|
|
|
|
|
} |
|
528
|
2
|
100
|
|
|
|
209
|
return ref $schema ? dclone($schema) : $schema; |
|
529
|
|
|
|
|
|
|
} |
|
530
|
|
|
|
|
|
|
} |
|
531
|
|
|
|
|
|
|
|
|
532
|
0
|
|
|
1
|
1
|
0
|
sub get_document ($self, $uri_reference) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
533
|
0
|
|
|
|
|
0
|
my $schema_info = $self->_fetch_from_uri($uri_reference); |
|
534
|
0
|
0
|
|
|
|
0
|
return if not $schema_info; |
|
535
|
0
|
|
|
|
|
0
|
return $schema_info->{document}; |
|
536
|
|
|
|
|
|
|
} |
|
537
|
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
# defined lower down: |
|
539
|
|
|
|
|
|
|
# sub add_media_type ($self, $media_type, $sub) { ... } |
|
540
|
|
|
|
|
|
|
# sub get_media_type ($self, $media_type) { ... } |
|
541
|
|
|
|
|
|
|
# sub add_encoding ($self, $encoding, $sub) { ... } |
|
542
|
|
|
|
|
|
|
# sub get_encoding ($self, $encoding) { ... } |
|
543
|
|
|
|
|
|
|
# sub add_vocabulary ($self, $classname) { ... } |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
######## NO PUBLIC INTERFACES FOLLOW THIS POINT ######## |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
# current spec version => { keyword => undef, or arrayref of alternatives } |
|
548
|
|
|
|
|
|
|
my %removed_keywords = ( |
|
549
|
|
|
|
|
|
|
'draft4' => { |
|
550
|
|
|
|
|
|
|
}, |
|
551
|
|
|
|
|
|
|
'draft6' => { |
|
552
|
|
|
|
|
|
|
id => [ '$id' ], |
|
553
|
|
|
|
|
|
|
}, |
|
554
|
|
|
|
|
|
|
'draft7' => { |
|
555
|
|
|
|
|
|
|
id => [ '$id' ], |
|
556
|
|
|
|
|
|
|
}, |
|
557
|
|
|
|
|
|
|
'draft2019-09' => { |
|
558
|
|
|
|
|
|
|
id => [ '$id' ], |
|
559
|
|
|
|
|
|
|
definitions => [ '$defs' ], |
|
560
|
|
|
|
|
|
|
dependencies => [ qw(dependentSchemas dependentRequired) ], |
|
561
|
|
|
|
|
|
|
}, |
|
562
|
|
|
|
|
|
|
'draft2020-12' => { |
|
563
|
|
|
|
|
|
|
id => [ '$id' ], |
|
564
|
|
|
|
|
|
|
definitions => [ '$defs' ], |
|
565
|
|
|
|
|
|
|
dependencies => [ qw(dependentSchemas dependentRequired) ], |
|
566
|
|
|
|
|
|
|
'$recursiveAnchor' => [ '$dynamicAnchor' ], |
|
567
|
|
|
|
|
|
|
'$recursiveRef' => [ '$dynamicRef' ], |
|
568
|
|
|
|
|
|
|
additionalItems => [ 'items' ], |
|
569
|
|
|
|
|
|
|
}, |
|
570
|
|
|
|
|
|
|
); |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# { |
|
573
|
|
|
|
|
|
|
# $spec_version => { |
|
574
|
|
|
|
|
|
|
# $vocabulary_class => { |
|
575
|
|
|
|
|
|
|
# traverse => [ [ $keyword => $subref ], [ ... ] ], |
|
576
|
|
|
|
|
|
|
# evaluate => [ [ $keyword => $subref ], [ ... ] ], |
|
577
|
|
|
|
|
|
|
# } |
|
578
|
|
|
|
|
|
|
# } |
|
579
|
|
|
|
|
|
|
# } |
|
580
|
|
|
|
|
|
|
# If we could serialize coderefs, this could be an object attribute; |
|
581
|
|
|
|
|
|
|
# otherwise, we might as well persist this for the lifetime of the process. |
|
582
|
|
|
|
|
|
|
our $vocabulary_cache = {}; |
|
583
|
|
|
|
|
|
|
|
|
584
|
42150
|
|
|
42151
|
|
93836
|
sub _traverse_subschema ($self, $schema, $state) { |
|
|
42150
|
|
|
|
|
76684
|
|
|
|
42150
|
|
|
|
|
76022
|
|
|
|
42150
|
|
|
|
|
71909
|
|
|
|
42150
|
|
|
|
|
75902
|
|
|
585
|
42150
|
|
|
|
|
500781
|
delete $state->@{'keyword', grep /^_/, keys %$state}; |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
return E($state, 'EXCEPTION: maximum traversal depth (%d) exceeded', $self->max_traversal_depth) |
|
588
|
42150
|
50
|
|
|
|
273098
|
if $state->{depth}++ > $self->max_traversal_depth; |
|
589
|
|
|
|
|
|
|
|
|
590
|
42150
|
|
|
|
|
248745
|
push $state->{subschemas}->@*, $state->{traversed_keyword_path}.$state->{keyword_path}; |
|
591
|
|
|
|
|
|
|
|
|
592
|
42150
|
|
|
|
|
184357
|
my $schema_type = get_type($schema); |
|
593
|
|
|
|
|
|
|
return 1 if $schema_type eq 'boolean' |
|
594
|
|
|
|
|
|
|
and ($state->{specification_version} ne 'draft4' |
|
595
|
42150
|
100
|
100
|
|
|
204895
|
or $state->{keyword_path} =~ m{/(?:additional(?:Items|Properties)|uniqueItems)\z}); |
|
|
|
|
100
|
|
|
|
|
|
596
|
|
|
|
|
|
|
|
|
597
|
34439
|
100
|
|
|
|
94763
|
return E($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object'; |
|
598
|
|
|
|
|
|
|
|
|
599
|
34422
|
100
|
|
|
|
139231
|
return 1 if not keys %$schema; |
|
600
|
|
|
|
|
|
|
|
|
601
|
33224
|
|
|
|
|
67640
|
my $valid = 1; |
|
602
|
33224
|
|
|
|
|
268335
|
my %unknown_keywords = map +($_ => undef), grep !/^x-/, keys %$schema; |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
# we use an index rather than iterating through the lists directly because the lists of |
|
605
|
|
|
|
|
|
|
# vocabularies and keywords can change after we have started. However, only the Core vocabulary |
|
606
|
|
|
|
|
|
|
# and $schema keyword can make this change, and they both come first, therefore a simple index |
|
607
|
|
|
|
|
|
|
# into the list is sufficient. |
|
608
|
|
|
|
|
|
|
ALL_KEYWORDS: |
|
609
|
33224
|
|
|
|
|
178827
|
for (my $vocab_index = 0; $vocab_index < $state->{vocabularies}->@*; $vocab_index++) { |
|
610
|
203199
|
|
|
|
|
495284
|
my $vocabulary = $state->{vocabularies}[$vocab_index]; |
|
611
|
203199
|
|
|
|
|
286588
|
my $keyword_list; |
|
612
|
|
|
|
|
|
|
|
|
613
|
203199
|
|
66
|
|
|
464923
|
for (my $keyword_index = 0; |
|
614
|
|
|
|
|
|
|
$keyword_index < ($keyword_list //= do { |
|
615
|
47
|
|
|
47
|
|
220865
|
use autovivification qw(fetch store); |
|
|
47
|
|
|
|
|
128
|
|
|
|
47
|
|
|
|
|
296
|
|
|
616
|
|
|
|
|
|
|
$vocabulary_cache->{$state->{specification_version}}{$vocabulary}{traverse} //= [ |
|
617
|
|
|
|
|
|
|
map [ $_ => $vocabulary->can('_traverse_keyword_'.($_ =~ s/^\$//r)) ], |
|
618
|
|
|
|
|
|
|
$vocabulary->keywords($state->{specification_version}) |
|
619
|
203267
|
|
100
|
|
|
1012364
|
]; |
|
620
|
|
|
|
|
|
|
})->@*; |
|
621
|
|
|
|
|
|
|
$keyword_index++) { |
|
622
|
1683582
|
|
|
|
|
3455415
|
my ($keyword, $sub) = $keyword_list->[$keyword_index]->@*; |
|
623
|
1683582
|
100
|
|
|
|
5558973
|
next if not exists $schema->{$keyword}; |
|
624
|
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
# keywords adjacent to $ref are not evaluated before draft2019-09 |
|
626
|
56825
|
100
|
100
|
|
|
324859
|
next if $keyword ne '$ref' and exists $schema->{'$ref'} and $state->{specification_version} =~ /^draft[467]\z/; |
|
|
|
|
100
|
|
|
|
|
|
627
|
|
|
|
|
|
|
|
|
628
|
56789
|
|
|
|
|
141294
|
delete $unknown_keywords{$keyword}; |
|
629
|
56789
|
|
|
|
|
156358
|
$state->{keyword} = $keyword; |
|
630
|
|
|
|
|
|
|
|
|
631
|
56789
|
|
|
|
|
132751
|
my $old_spec_version = $state->{specification_version}; |
|
632
|
56789
|
|
|
|
|
140893
|
my $error_count = $state->{errors}->@*; |
|
633
|
|
|
|
|
|
|
|
|
634
|
56789
|
100
|
|
|
|
253620
|
if (not $sub->($vocabulary, $schema, $state)) { |
|
635
|
|
|
|
|
|
|
die 'traverse result is false but there are no errors (keyword: '.$keyword.')' |
|
636
|
243
|
50
|
|
|
|
1167
|
if $error_count == $state->{errors}->@*; |
|
637
|
243
|
|
|
|
|
489
|
$valid = 0; |
|
638
|
243
|
|
|
|
|
1317
|
next; |
|
639
|
|
|
|
|
|
|
} |
|
640
|
|
|
|
|
|
|
warn 'traverse result is true but there are errors ('.$keyword.': '.$state->{errors}[-1]->error |
|
641
|
56546
|
50
|
|
|
|
300593
|
if $error_count != $state->{errors}->@*; |
|
642
|
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
# a keyword changed the keyword list for this vocabulary; re-fetch the list before continuing |
|
644
|
56546
|
100
|
|
|
|
172733
|
undef $keyword_list if $state->{specification_version} ne $old_spec_version; |
|
645
|
|
|
|
|
|
|
|
|
646
|
56546
|
100
|
|
|
|
341748
|
if (my $callback = $state->{callbacks}{$keyword}) { |
|
647
|
4
|
|
|
|
|
12
|
$error_count = $state->{errors}->@*; |
|
648
|
|
|
|
|
|
|
|
|
649
|
4
|
50
|
|
|
|
20
|
if (not $callback->($schema, $state)) { |
|
650
|
|
|
|
|
|
|
die 'callback result is false but there are no errors (keyword: '.$keyword.')' |
|
651
|
0
|
0
|
|
|
|
0
|
if $error_count == $state->{errors}->@*; |
|
652
|
0
|
|
|
|
|
0
|
$valid = 0; |
|
653
|
0
|
|
|
|
|
0
|
next; |
|
654
|
|
|
|
|
|
|
} |
|
655
|
|
|
|
|
|
|
die 'callback result is true but there are errors (keyword: '.$keyword.')' |
|
656
|
4
|
50
|
|
|
|
4015
|
if $error_count != $state->{errors}->@*; |
|
657
|
|
|
|
|
|
|
} |
|
658
|
|
|
|
|
|
|
} |
|
659
|
|
|
|
|
|
|
} |
|
660
|
|
|
|
|
|
|
|
|
661
|
33224
|
|
|
|
|
115085
|
delete $state->{keyword}; |
|
662
|
|
|
|
|
|
|
|
|
663
|
33224
|
100
|
100
|
|
|
154660
|
if ($self->strict and keys %unknown_keywords) { |
|
664
|
2
|
50
|
|
|
|
22
|
$valid = E($state, 'unknown keyword%s seen in schema: %s', keys %unknown_keywords > 1 ? 's' : '', |
|
665
|
|
|
|
|
|
|
join(', ', sort keys %unknown_keywords)); |
|
666
|
|
|
|
|
|
|
} |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# check for previously-supported but now removed keywords |
|
669
|
33224
|
|
|
|
|
271238
|
foreach my $keyword (sort keys $removed_keywords{$state->{specification_version}}->%*) { |
|
670
|
103924
|
100
|
|
|
|
262760
|
next if not exists $schema->{$keyword}; |
|
671
|
224
|
|
|
|
|
1208
|
my $message ='no-longer-supported "'.$keyword.'" keyword present (at location "' |
|
672
|
|
|
|
|
|
|
.canonical_uri($state).'")'; |
|
673
|
224
|
50
|
|
|
|
33526
|
if (my $alternates = $removed_keywords{$state->{specification_version}}->{$keyword}) { |
|
674
|
224
|
|
|
|
|
1431
|
my @list = map '"'.$_.'"', @$alternates; |
|
675
|
224
|
50
|
|
|
|
837
|
@list = ((map $_.',', @list[0..$#list-1]), $list[-1]) if @list > 2; |
|
676
|
224
|
100
|
|
|
|
1012
|
splice(@list, -1, 0, 'or') if @list > 1; |
|
677
|
224
|
|
|
|
|
866
|
$message .= ': this should be rewritten as '.join(' ', @list); |
|
678
|
|
|
|
|
|
|
} |
|
679
|
224
|
|
|
|
|
74847
|
carp $message; |
|
680
|
|
|
|
|
|
|
} |
|
681
|
|
|
|
|
|
|
|
|
682
|
33224
|
|
|
|
|
246236
|
return $valid; |
|
683
|
|
|
|
|
|
|
} |
|
684
|
|
|
|
|
|
|
|
|
685
|
34815
|
|
|
34816
|
|
70649
|
sub _eval_subschema ($self, $data, $schema, $state) { |
|
|
34815
|
|
|
|
|
65176
|
|
|
|
34815
|
|
|
|
|
71054
|
|
|
|
34815
|
|
|
|
|
61824
|
|
|
|
34815
|
|
|
|
|
59144
|
|
|
|
34815
|
|
|
|
|
54367
|
|
|
686
|
34815
|
50
|
|
|
|
94381
|
croak '_eval_subschema called in void context' if not defined wantarray; |
|
687
|
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
# callers created a new $state for us, so we do not propagate upwards changes to depth, traversed |
|
689
|
|
|
|
|
|
|
# paths; but annotations, errors are arrayrefs so their contents will be shared |
|
690
|
34815
|
|
100
|
|
|
199398
|
$state->{dynamic_scope} = [ ($state->{dynamic_scope}//[])->@* ]; |
|
691
|
34815
|
|
|
|
|
492629
|
delete $state->@{'keyword', grep /^_/, keys %$state}; |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
abort($state, 'EXCEPTION: maximum evaluation depth (%d) exceeded', $self->max_traversal_depth) |
|
694
|
34815
|
100
|
|
|
|
222803
|
if $state->{depth}++ > $self->max_traversal_depth; |
|
695
|
|
|
|
|
|
|
|
|
696
|
34812
|
|
|
|
|
156220
|
my $schema_type = get_type($schema); |
|
697
|
34812
|
100
|
66
|
|
|
109143
|
return $schema || E($state, 'subschema is false') if $schema_type eq 'boolean'; |
|
698
|
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
# this should never happen, due to checks in traverse |
|
700
|
33968
|
50
|
|
|
|
96450
|
abort($state, 'invalid schema type: %s', $schema_type) if $schema_type ne 'object'; |
|
701
|
|
|
|
|
|
|
|
|
702
|
33968
|
100
|
|
|
|
137248
|
return 1 if not keys %$schema; |
|
703
|
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
# find all schema locations in effect at this data path + uri combination |
|
705
|
|
|
|
|
|
|
# if any of them are absolute prefix of this schema location, we are in a loop. |
|
706
|
33331
|
|
|
|
|
126302
|
my $canonical_uri = canonical_uri($state); |
|
707
|
33331
|
|
|
|
|
146411
|
my $schema_location = $state->{traversed_keyword_path}.$state->{keyword_path}; |
|
708
|
|
|
|
|
|
|
{ |
|
709
|
47
|
|
|
47
|
|
73494
|
use autovivification qw(fetch store); |
|
|
47
|
|
|
|
|
164
|
|
|
|
47
|
|
|
|
|
288
|
|
|
|
33331
|
|
|
|
|
56805
|
|
|
710
|
|
|
|
|
|
|
abort($state, 'EXCEPTION: infinite loop detected (same location evaluated twice)') |
|
711
|
|
|
|
|
|
|
if grep substr($schema_location, 0, length) eq $_, |
|
712
|
33331
|
100
|
|
|
|
222421
|
keys $state->{seen}{$state->{data_path}}{$canonical_uri}->%*; |
|
713
|
33330
|
|
|
|
|
6245386
|
$state->{seen}{$state->{data_path}}{$canonical_uri}{$schema_location}++; |
|
714
|
|
|
|
|
|
|
} |
|
715
|
|
|
|
|
|
|
|
|
716
|
33330
|
|
|
|
|
5379540
|
my $valid = 1; |
|
717
|
33330
|
|
|
|
|
337767
|
my %unknown_keywords = map +($_ => undef), grep !/^x-/, keys %$schema; |
|
718
|
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
# set aside annotations collected so far; they are not used in the current scope's evaluation |
|
720
|
33330
|
|
|
|
|
127762
|
my $parent_annotations = $state->{annotations}; |
|
721
|
33330
|
|
|
|
|
104482
|
$state->{annotations} = []; |
|
722
|
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
# in order to collect annotations from applicator keywords only when needed, we twiddle the low |
|
724
|
|
|
|
|
|
|
# bit if we see a local unevaluated* keyword, and clear it again as we move on to a new data path. |
|
725
|
|
|
|
|
|
|
# We also set it when _strict_schema_data is set, but only for object data instances. |
|
726
|
|
|
|
|
|
|
$state->{collect_annotations} |= |
|
727
|
|
|
|
|
|
|
0+((ref $data eq 'ARRAY' && exists $schema->{unevaluatedItems}) |
|
728
|
|
|
|
|
|
|
|| ((my $is_object_data = ref $data eq 'HASH') |
|
729
|
33330
|
|
100
|
|
|
395771
|
&& (exists $schema->{unevaluatedProperties} || !!$state->{seen_data_properties}))); |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
# set aside defaults collected so far; we need to keep the subschema's defaults separated in |
|
732
|
|
|
|
|
|
|
# case they must be discarded due to overall invalidity of the subschema |
|
733
|
33330
|
|
|
|
|
102268
|
my $defaults = $state->{defaults}; |
|
734
|
33330
|
100
|
|
|
|
105153
|
$state->{defaults} = {} if $state->{defaults}; |
|
735
|
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
# we use an index rather than iterating through the lists directly because the lists of |
|
737
|
|
|
|
|
|
|
# vocabularies and keywords can change after we have started. However, only the Core vocabulary |
|
738
|
|
|
|
|
|
|
# and $schema keyword can make this change, and they both come first, therefore a simple index |
|
739
|
|
|
|
|
|
|
# into the list is sufficient. |
|
740
|
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
ALL_KEYWORDS: |
|
742
|
33330
|
|
|
|
|
159927
|
for (my $vocab_index = 0; $vocab_index < $state->{vocabularies}->@*; $vocab_index++) { |
|
743
|
182959
|
|
|
|
|
444262
|
my $vocabulary = $state->{vocabularies}[$vocab_index]; |
|
744
|
182959
|
|
|
|
|
265445
|
my $keyword_list; |
|
745
|
|
|
|
|
|
|
|
|
746
|
182959
|
|
66
|
|
|
424234
|
for (my $keyword_index = 0; |
|
747
|
|
|
|
|
|
|
$keyword_index < ($keyword_list //= do { |
|
748
|
47
|
|
|
47
|
|
22179
|
use autovivification qw(fetch store); |
|
|
47
|
|
|
|
|
158
|
|
|
|
47
|
|
|
|
|
283
|
|
|
749
|
|
|
|
|
|
|
$vocabulary_cache->{$state->{specification_version}}{$vocabulary}{evaluate} //= [ |
|
750
|
|
|
|
|
|
|
map [ $_ => $vocabulary->can('_eval_keyword_'.($_ =~ s/^\$//r)) ], |
|
751
|
|
|
|
|
|
|
$vocabulary->keywords($state->{specification_version}) |
|
752
|
182962
|
|
100
|
|
|
995752
|
]; |
|
753
|
|
|
|
|
|
|
})->@*; |
|
754
|
|
|
|
|
|
|
$keyword_index++) { |
|
755
|
1509373
|
|
|
|
|
3102076
|
my ($keyword, $sub) = $keyword_list->[$keyword_index]->@*; |
|
756
|
1509373
|
100
|
|
|
|
5031938
|
next if not exists $schema->{$keyword}; |
|
757
|
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
# keywords adjacent to $ref are not evaluated before draft2019-09 |
|
759
|
60004
|
100
|
100
|
|
|
334039
|
next if $keyword ne '$ref' and exists $schema->{'$ref'} and $state->{specification_version} =~ /^draft[467]\z/; |
|
|
|
|
100
|
|
|
|
|
|
760
|
|
|
|
|
|
|
|
|
761
|
59973
|
|
|
|
|
153921
|
delete $unknown_keywords{$keyword}; |
|
762
|
59973
|
100
|
100
|
|
|
176029
|
next if not $valid and $state->{short_circuit} and $state->{strict}; |
|
|
|
|
66
|
|
|
|
|
|
763
|
|
|
|
|
|
|
|
|
764
|
59972
|
|
|
|
|
179418
|
$state->{keyword} = $keyword; |
|
765
|
|
|
|
|
|
|
|
|
766
|
59972
|
100
|
|
|
|
136005
|
if ($sub) { |
|
767
|
55896
|
|
|
|
|
139172
|
my $old_spec_version = $state->{specification_version}; |
|
768
|
55896
|
|
|
|
|
154003
|
my $error_count = $state->{errors}->@*; |
|
769
|
|
|
|
|
|
|
|
|
770
|
55896
|
|
|
|
|
133433
|
try { |
|
771
|
55896
|
100
|
|
|
|
264322
|
if (not $sub->($vocabulary, $data, $schema, $state)) { |
|
772
|
|
|
|
|
|
|
warn 'evaluation result is false but there are no errors (keyword: '.$keyword.')' |
|
773
|
13143
|
50
|
|
|
|
63467
|
if $error_count == $state->{errors}->@*; |
|
774
|
13143
|
|
|
|
|
24827
|
$valid = 0; |
|
775
|
|
|
|
|
|
|
|
|
776
|
13143
|
100
|
100
|
|
|
92425
|
last ALL_KEYWORDS if $state->{short_circuit} and not $state->{strict}; |
|
777
|
6939
|
|
|
|
|
42078
|
next; |
|
778
|
|
|
|
|
|
|
} |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
warn 'evaluation result is true but there are errors (keyword: '.$keyword.')' |
|
781
|
42709
|
50
|
|
|
|
443564
|
if $error_count != $state->{errors}->@*; |
|
782
|
|
|
|
|
|
|
} |
|
783
|
|
|
|
|
|
|
catch ($e) { |
|
784
|
44
|
100
|
|
|
|
961
|
die $e if $e->$_isa('JSON::Schema::Modern::Error'); |
|
785
|
2
|
|
|
|
|
32
|
abort($state, 'EXCEPTION: '.$e); |
|
786
|
|
|
|
|
|
|
} |
|
787
|
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
# a keyword changed the keyword list for this vocabulary; re-fetch the list before continuing |
|
789
|
42709
|
100
|
|
|
|
153803
|
undef $keyword_list if $state->{specification_version} ne $old_spec_version; |
|
790
|
|
|
|
|
|
|
} |
|
791
|
|
|
|
|
|
|
|
|
792
|
46785
|
100
|
100
|
|
|
291490
|
if (my $callback = ($state->{callbacks}//{})->{$keyword}) { |
|
793
|
19
|
|
|
|
|
53
|
my $error_count = $state->{errors}->@*; |
|
794
|
|
|
|
|
|
|
|
|
795
|
19
|
100
|
|
|
|
89
|
if (not $callback->($data, $schema, $state)) { |
|
796
|
|
|
|
|
|
|
warn 'callback result is false but there are no errors (keyword: '.$keyword.')' |
|
797
|
2
|
50
|
|
|
|
12
|
if $error_count == $state->{errors}->@*; |
|
798
|
2
|
|
|
|
|
7
|
$valid = 0; |
|
799
|
|
|
|
|
|
|
|
|
800
|
2
|
100
|
66
|
|
|
17
|
last ALL_KEYWORDS if $state->{short_circuit} and not $state->{strict}; |
|
801
|
1
|
|
|
|
|
6
|
next; |
|
802
|
|
|
|
|
|
|
} |
|
803
|
|
|
|
|
|
|
warn 'callback result is true but there are errors (keyword: '.$keyword.')' |
|
804
|
17
|
50
|
|
|
|
278
|
if $error_count != $state->{errors}->@*; |
|
805
|
|
|
|
|
|
|
} |
|
806
|
|
|
|
|
|
|
} |
|
807
|
|
|
|
|
|
|
} |
|
808
|
|
|
|
|
|
|
|
|
809
|
33286
|
|
|
|
|
136241
|
delete $state->{keyword}; |
|
810
|
|
|
|
|
|
|
|
|
811
|
33286
|
100
|
100
|
|
|
111659
|
if ($state->{strict} and keys %unknown_keywords) { |
|
812
|
3
|
100
|
|
|
|
41
|
abort($state, 'unknown keyword%s seen in schema: %s', keys %unknown_keywords > 1 ? 's' : '', |
|
813
|
|
|
|
|
|
|
join(', ', sort keys %unknown_keywords)); |
|
814
|
|
|
|
|
|
|
} |
|
815
|
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
# Note: we can remove all of this entirely and just rely on strict mode when we (eventually!) remove |
|
817
|
|
|
|
|
|
|
# the traverse phase and replace with evaluate-against-metaschema. |
|
818
|
33283
|
100
|
100
|
|
|
138371
|
if ($state->{seen_data_properties} and $is_object_data) { |
|
819
|
|
|
|
|
|
|
# record the locations of all local properties |
|
820
|
|
|
|
|
|
|
$state->{seen_data_properties}{jsonp($state->{data_path}, $_)} |= 0 |
|
821
|
156
|
|
|
|
|
1805
|
foreach grep !/^x-/, keys %$data; |
|
822
|
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
my @evaluated_properties = map { |
|
824
|
156
|
|
|
|
|
552
|
my $keyword = $_->{keyword}; |
|
|
577
|
|
|
|
|
1158
|
|
|
825
|
|
|
|
|
|
|
(grep $keyword eq $_, qw(properties additionalProperties patternProperties unevaluatedProperties)) |
|
826
|
577
|
100
|
|
|
|
2010
|
? $_->{annotation}->@* : (); |
|
827
|
|
|
|
|
|
|
} local_annotations($state); |
|
828
|
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
# tick off properties that were recognized by this subschema |
|
830
|
156
|
|
|
|
|
641
|
$state->{seen_data_properties}{jsonp($state->{data_path}, $_)} |= 1 foreach @evaluated_properties; |
|
831
|
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
# weird! the draft4 metaschema doesn't know about '$ref' at all! |
|
833
|
|
|
|
|
|
|
$state->{seen_data_properties}{$state->{data_path}.'/$ref'} |= 1 |
|
834
|
156
|
100
|
66
|
|
|
735
|
if exists $data->{'$ref'} and $state->{specification_version} eq 'draft4'; |
|
835
|
|
|
|
|
|
|
} |
|
836
|
|
|
|
|
|
|
|
|
837
|
33283
|
100
|
100
|
|
|
164388
|
if ($valid and $state->{collect_annotations} and $state->{specification_version} !~ /^draft(?:[467]|2019-09)\z/) { |
|
|
|
|
100
|
|
|
|
|
|
838
|
|
|
|
|
|
|
annotate_self(+{ %$state, keyword => $_, _unknown => 1 }, $schema) |
|
839
|
969
|
|
|
|
|
4229
|
foreach sort keys %unknown_keywords; |
|
840
|
|
|
|
|
|
|
} |
|
841
|
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
# only keep new annotations if schema is valid |
|
843
|
33283
|
100
|
|
|
|
138685
|
push $parent_annotations->@*, $state->{annotations}->@* if $valid; |
|
844
|
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
# only keep new defaults if schema is valid |
|
846
|
|
|
|
|
|
|
$defaults->@{keys $state->{defaults}->%*} = values $state->{defaults}->%* |
|
847
|
33283
|
100
|
100
|
|
|
131105
|
if $valid and $state->{defaults}; |
|
848
|
|
|
|
|
|
|
|
|
849
|
33283
|
|
|
|
|
367011
|
return $valid; |
|
850
|
|
|
|
|
|
|
} |
|
851
|
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
has _resource_index => ( |
|
853
|
|
|
|
|
|
|
is => 'bare', |
|
854
|
|
|
|
|
|
|
isa => Map[my $resource_key_type = Str->where('!/#/'), my $resource_type = Dict[ |
|
855
|
|
|
|
|
|
|
canonical_uri => (InstanceOf['Mojo::URL'])->where(q{not defined $_->fragment}), |
|
856
|
|
|
|
|
|
|
path => json_pointer_type, # JSON pointer relative to the document root |
|
857
|
|
|
|
|
|
|
specification_version => my $spec_version_type = Enum(SPECIFICATION_VERSIONS_SUPPORTED), |
|
858
|
|
|
|
|
|
|
document => InstanceOf['JSON::Schema::Modern::Document'], |
|
859
|
|
|
|
|
|
|
# the vocabularies used when evaluating instance data against schema |
|
860
|
|
|
|
|
|
|
vocabularies => ArrayRef[my $vocabulary_class_type = ClassName->where(q{$_->DOES('JSON::Schema::Modern::Vocabulary')})], |
|
861
|
|
|
|
|
|
|
anchors => Optional[HashRef[Dict[ |
|
862
|
|
|
|
|
|
|
canonical_uri => canonical_uri_type, # equivalent uri with json pointer fragment |
|
863
|
|
|
|
|
|
|
path => json_pointer_type, # JSON pointer relative to the document root |
|
864
|
|
|
|
|
|
|
dynamic => Optional[Bool], |
|
865
|
|
|
|
|
|
|
]]], |
|
866
|
|
|
|
|
|
|
Slurpy[HashRef[Undef]], # no other fields allowed |
|
867
|
|
|
|
|
|
|
]], |
|
868
|
|
|
|
|
|
|
); |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
sub _get_resource { |
|
871
|
44006
|
50
|
|
44007
|
|
164807
|
die 'bad resource: ', $_[1] if $_[1] =~ /#/; |
|
872
|
44006
|
|
100
|
|
|
4248219
|
($_[0]->{_resource_index}//{})->{$_[1]} |
|
873
|
|
|
|
|
|
|
} |
|
874
|
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
# does not check for duplicate entries, or for malformed uris |
|
876
|
|
|
|
|
|
|
sub _add_resources_unsafe { |
|
877
|
47
|
|
|
47
|
|
103454
|
use autovivification 'store'; |
|
|
47
|
|
|
|
|
146
|
|
|
|
47
|
|
|
|
|
325
|
|
|
878
|
|
|
|
|
|
|
$_[0]->{_resource_index}{$resource_key_type->($_->[0])} = $resource_type->($_->[1]) |
|
879
|
103
|
|
|
104
|
|
1376
|
foreach pairs @_[1..$#_]; |
|
880
|
|
|
|
|
|
|
} |
|
881
|
25
|
|
50
|
26
|
|
84796
|
sub _resource_index { ($_[0]->{_resource_index}//{})->%* } |
|
882
|
17381
|
|
100
|
17382
|
|
4220653
|
sub _canonical_resources { values(($_[0]->{_resource_index}//{})->%*) } |
|
883
|
2121
|
|
50
|
2122
|
|
373296
|
sub _resource_pairs { pairs(($_[0]->{_resource_index}//{})->%*) } |
|
884
|
|
|
|
|
|
|
|
|
885
|
18963
|
|
|
18964
|
|
69389
|
sub _add_resource ($self, @kvs) { |
|
|
18963
|
|
|
|
|
39941
|
|
|
|
18963
|
|
|
|
|
51402
|
|
|
|
18963
|
|
|
|
|
35993
|
|
|
886
|
18963
|
|
|
|
|
123748
|
foreach my $pair (sort { $a->[0] cmp $b->[0] } pairs @kvs) { |
|
|
0
|
|
|
|
|
0
|
|
|
887
|
18963
|
|
|
|
|
55032
|
my ($canonical_uri, $resource) = @$pair; |
|
888
|
|
|
|
|
|
|
|
|
889
|
18963
|
100
|
|
|
|
81701
|
if (my $existing = $self->_get_resource($canonical_uri)) { |
|
|
|
100
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
# we allow overwriting canonical_uri = '' to allow for ad hoc evaluation of schemas that |
|
891
|
|
|
|
|
|
|
# lack all identifiers altogether, but preserve other resources from the original document |
|
892
|
17042
|
100
|
|
|
|
77587
|
if ($canonical_uri ne '') { |
|
893
|
|
|
|
|
|
|
my @diffs = ( |
|
894
|
|
|
|
|
|
|
($existing->{path} eq $resource->{path} ? () : 'path'), |
|
895
|
|
|
|
|
|
|
($existing->{canonical_uri} eq $resource->{canonical_uri} ? () : 'canonical_uri'), |
|
896
|
|
|
|
|
|
|
($existing->{specification_version} eq $resource->{specification_version} ? () : 'specification_version'), |
|
897
|
859
|
100
|
|
|
|
9365
|
(refaddr($existing->{document}) == refaddr($resource->{document}) ? () : 'refaddr')); |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
898
|
859
|
100
|
|
|
|
392644
|
next if not @diffs; |
|
899
|
10
|
|
|
|
|
3604
|
croak 'uri "'.$canonical_uri.'" conflicts with an existing schema resource: documents differ by ', |
|
900
|
|
|
|
|
|
|
join(', ', @diffs); |
|
901
|
|
|
|
|
|
|
} |
|
902
|
|
|
|
|
|
|
} |
|
903
|
|
|
|
|
|
|
elsif (JSON::Schema::Modern::Utilities::get_schema_filename($canonical_uri)) { |
|
904
|
2
|
|
|
|
|
708
|
croak 'uri "'.$canonical_uri.'" conflicts with an existing cached schema resource'; |
|
905
|
|
|
|
|
|
|
} |
|
906
|
|
|
|
|
|
|
|
|
907
|
47
|
|
|
47
|
|
36443
|
use autovivification 'store'; |
|
|
47
|
|
|
|
|
156
|
|
|
|
47
|
|
|
|
|
319
|
|
|
908
|
18102
|
|
|
|
|
115845
|
$self->{_resource_index}{$resource_key_type->($canonical_uri)} = $resource_type->($resource); |
|
909
|
|
|
|
|
|
|
} |
|
910
|
|
|
|
|
|
|
} |
|
911
|
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
# $vocabulary uri (not its $id!) => [ specification_version, class ] |
|
913
|
|
|
|
|
|
|
has _vocabulary_classes => ( |
|
914
|
|
|
|
|
|
|
is => 'bare', |
|
915
|
|
|
|
|
|
|
isa => HashRef[ |
|
916
|
|
|
|
|
|
|
my $vocabulary_type = Tuple[ |
|
917
|
|
|
|
|
|
|
$spec_version_type, |
|
918
|
|
|
|
|
|
|
$vocabulary_class_type, |
|
919
|
|
|
|
|
|
|
] |
|
920
|
|
|
|
|
|
|
], |
|
921
|
|
|
|
|
|
|
reader => '__vocabulary_classes', |
|
922
|
|
|
|
|
|
|
lazy => 1, |
|
923
|
|
|
|
|
|
|
default => sub { |
|
924
|
|
|
|
|
|
|
+{ |
|
925
|
12
|
|
|
13
|
|
1196
|
map { my $class = $_; pairmap { $a => [ $b, $class ] } $class->vocabulary } |
|
|
12
|
|
|
13
|
|
32
|
|
|
|
12
|
|
|
13
|
|
370
|
|
|
|
12
|
|
|
12
|
|
817
|
|
|
|
12
|
|
|
12
|
|
44
|
|
|
|
12
|
|
|
12
|
|
422
|
|
|
|
12
|
|
|
12
|
|
866
|
|
|
|
12
|
|
|
12
|
|
35
|
|
|
|
12
|
|
|
7
|
|
312
|
|
|
|
12
|
|
|
7
|
|
911
|
|
|
|
12
|
|
|
7
|
|
31
|
|
|
|
12
|
|
|
7
|
|
212
|
|
|
|
12
|
|
|
7
|
|
850
|
|
|
|
12
|
|
|
7
|
|
30
|
|
|
|
12
|
|
|
7
|
|
287
|
|
|
|
12
|
|
|
7
|
|
773
|
|
|
|
12
|
|
|
1
|
|
29
|
|
|
|
12
|
|
|
1
|
|
293
|
|
|
|
12
|
|
|
1
|
|
80
|
|
|
|
12
|
|
|
1
|
|
28
|
|
|
|
12
|
|
|
1
|
|
208
|
|
|
|
12
|
|
|
1
|
|
74
|
|
|
|
12
|
|
|
|
|
32
|
|
|
|
12
|
|
|
|
|
54
|
|
|
|
7
|
|
|
|
|
61
|
|
|
|
7
|
|
|
|
|
18
|
|
|
|
7
|
|
|
|
|
169
|
|
|
|
7
|
|
|
|
|
41
|
|
|
|
7
|
|
|
|
|
17
|
|
|
|
7
|
|
|
|
|
127
|
|
|
|
7
|
|
|
|
|
34
|
|
|
|
7
|
|
|
|
|
16
|
|
|
|
7
|
|
|
|
|
120
|
|
|
|
7
|
|
|
|
|
40
|
|
|
|
7
|
|
|
|
|
16
|
|
|
|
7
|
|
|
|
|
136
|
|
|
|
7
|
|
|
|
|
55
|
|
|
|
7
|
|
|
|
|
18
|
|
|
|
7
|
|
|
|
|
142
|
|
|
|
7
|
|
|
|
|
33
|
|
|
|
7
|
|
|
|
|
18
|
|
|
|
7
|
|
|
|
|
122
|
|
|
|
7
|
|
|
|
|
34
|
|
|
|
7
|
|
|
|
|
14
|
|
|
|
7
|
|
|
|
|
165
|
|
|
|
7
|
|
|
|
|
42
|
|
|
|
7
|
|
|
|
|
19
|
|
|
|
7
|
|
|
|
|
47
|
|
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
14
|
|
|
|
1
|
|
|
|
|
6
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
32
|
|
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
14
|
|
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
27
|
|
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
13
|
|
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
5
|
|
|
926
|
|
|
|
|
|
|
map load_module('JSON::Schema::Modern::Vocabulary::'.$_), |
|
927
|
|
|
|
|
|
|
qw(Core Applicator Validation FormatAssertion FormatAnnotation Content MetaData Unevaluated) |
|
928
|
|
|
|
|
|
|
} |
|
929
|
|
|
|
|
|
|
}, |
|
930
|
|
|
|
|
|
|
); |
|
931
|
|
|
|
|
|
|
|
|
932
|
107
|
|
|
108
|
|
4657
|
sub _get_vocabulary_class { $_[0]->__vocabulary_classes->{$_[1]} } |
|
933
|
|
|
|
|
|
|
|
|
934
|
10
|
|
|
11
|
1
|
37880
|
sub add_vocabulary ($self, $classname) { |
|
|
10
|
|
|
|
|
24
|
|
|
|
10
|
|
|
|
|
24
|
|
|
|
10
|
|
|
|
|
19
|
|
|
935
|
10
|
50
|
|
|
|
359
|
return if grep $_->[1] eq $classname, values $self->__vocabulary_classes->%*; |
|
936
|
|
|
|
|
|
|
|
|
937
|
10
|
|
|
|
|
3243
|
$vocabulary_class_type->(load_module($classname)); |
|
938
|
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
# uri => version, uri => version |
|
940
|
7
|
|
|
|
|
858
|
foreach my $pair (pairs $classname->vocabulary) { |
|
941
|
7
|
|
|
|
|
162
|
my ($uri_string, $spec_version) = @$pair; |
|
942
|
7
|
|
|
|
|
66
|
Str->where(q{my $uri = Mojo::URL->new($_); $uri->is_abs && !defined $uri->fragment})->($uri_string); |
|
943
|
6
|
|
|
|
|
11271
|
$spec_version_type->($spec_version); |
|
944
|
|
|
|
|
|
|
|
|
945
|
4
|
100
|
|
|
|
687
|
croak 'keywords starting with "$" are reserved for core and cannot be used' |
|
946
|
|
|
|
|
|
|
if grep /^\$/, $classname->keywords; |
|
947
|
|
|
|
|
|
|
|
|
948
|
3
|
|
|
|
|
36
|
$self->{_vocabulary_classes}{$uri_string} = $vocabulary_type->([ $spec_version, $classname ]); |
|
949
|
|
|
|
|
|
|
} |
|
950
|
|
|
|
|
|
|
} |
|
951
|
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
# $schema uri => [ specification_version, [ vocab classes, in evaluation order ] ]. |
|
953
|
|
|
|
|
|
|
has _metaschema_vocabulary_classes => ( |
|
954
|
|
|
|
|
|
|
is => 'bare', |
|
955
|
|
|
|
|
|
|
isa => HashRef[ |
|
956
|
|
|
|
|
|
|
my $mvc_type = Tuple[ |
|
957
|
|
|
|
|
|
|
$spec_version_type, |
|
958
|
|
|
|
|
|
|
ArrayRef[$vocabulary_class_type], |
|
959
|
|
|
|
|
|
|
] |
|
960
|
|
|
|
|
|
|
], |
|
961
|
|
|
|
|
|
|
reader => '__metaschema_vocabulary_classes', |
|
962
|
|
|
|
|
|
|
lazy => 1, |
|
963
|
|
|
|
|
|
|
default => sub { |
|
964
|
38
|
|
|
38
|
|
25968
|
my @modules = map load_module('JSON::Schema::Modern::Vocabulary::'.$_), |
|
|
38
|
|
|
38
|
|
189
|
|
|
|
38
|
|
|
38
|
|
1517
|
|
|
|
38
|
|
|
38
|
|
22532
|
|
|
|
38
|
|
|
38
|
|
264
|
|
|
|
38
|
|
|
38
|
|
1722
|
|
|
|
38
|
|
|
38
|
|
25885
|
|
|
|
38
|
|
|
30
|
|
231
|
|
|
|
38
|
|
|
30
|
|
1656
|
|
|
|
38
|
|
|
30
|
|
25254
|
|
|
|
38
|
|
|
30
|
|
237
|
|
|
|
38
|
|
|
30
|
|
1569
|
|
|
|
38
|
|
|
30
|
|
23985
|
|
|
|
38
|
|
|
30
|
|
187
|
|
|
|
38
|
|
|
19
|
|
1617
|
|
|
|
38
|
|
|
19
|
|
30131
|
|
|
|
38
|
|
|
19
|
|
437
|
|
|
|
38
|
|
|
19
|
|
1463
|
|
|
|
38
|
|
|
19
|
|
328
|
|
|
|
38
|
|
|
19
|
|
81
|
|
|
|
38
|
|
|
19
|
|
238
|
|
|
|
30
|
|
|
16
|
|
265
|
|
|
|
30
|
|
|
15
|
|
84
|
|
|
|
30
|
|
|
15
|
|
631
|
|
|
|
30
|
|
|
15
|
|
170
|
|
|
|
30
|
|
|
15
|
|
67
|
|
|
|
30
|
|
|
15
|
|
653
|
|
|
|
30
|
|
|
15
|
|
177
|
|
|
|
30
|
|
|
13
|
|
80
|
|
|
|
30
|
|
|
13
|
|
508
|
|
|
|
30
|
|
|
13
|
|
134
|
|
|
|
30
|
|
|
13
|
|
95
|
|
|
|
30
|
|
|
13
|
|
681
|
|
|
|
30
|
|
|
13
|
|
260
|
|
|
|
30
|
|
|
13
|
|
63
|
|
|
|
30
|
|
|
12
|
|
813
|
|
|
|
30
|
|
|
12
|
|
152
|
|
|
|
30
|
|
|
12
|
|
67
|
|
|
|
30
|
|
|
12
|
|
556
|
|
|
|
30
|
|
|
12
|
|
143
|
|
|
|
30
|
|
|
12
|
|
60
|
|
|
|
30
|
|
|
12
|
|
171
|
|
|
|
19
|
|
|
11
|
|
240
|
|
|
|
19
|
|
|
11
|
|
64
|
|
|
|
19
|
|
|
11
|
|
479
|
|
|
|
19
|
|
|
11
|
|
131
|
|
|
|
19
|
|
|
11
|
|
38
|
|
|
|
19
|
|
|
11
|
|
325
|
|
|
|
19
|
|
|
11
|
|
102
|
|
|
|
19
|
|
|
10
|
|
43
|
|
|
|
19
|
|
|
10
|
|
447
|
|
|
|
19
|
|
|
10
|
|
119
|
|
|
|
19
|
|
|
9
|
|
37
|
|
|
|
19
|
|
|
9
|
|
405
|
|
|
|
19
|
|
|
9
|
|
98
|
|
|
|
19
|
|
|
9
|
|
38
|
|
|
|
19
|
|
|
9
|
|
374
|
|
|
|
19
|
|
|
9
|
|
89
|
|
|
|
19
|
|
|
9
|
|
43
|
|
|
|
19
|
|
|
9
|
|
359
|
|
|
|
19
|
|
|
9
|
|
86
|
|
|
|
19
|
|
|
9
|
|
37
|
|
|
|
19
|
|
|
9
|
|
130
|
|
|
|
16
|
|
|
9
|
|
124
|
|
|
|
16
|
|
|
9
|
|
38
|
|
|
|
16
|
|
|
9
|
|
423
|
|
|
|
15
|
|
|
9
|
|
76
|
|
|
|
15
|
|
|
9
|
|
33
|
|
|
|
15
|
|
|
9
|
|
367
|
|
|
|
15
|
|
|
9
|
|
80
|
|
|
|
15
|
|
|
8
|
|
35
|
|
|
|
15
|
|
|
8
|
|
250
|
|
|
|
15
|
|
|
8
|
|
67
|
|
|
|
15
|
|
|
8
|
|
28
|
|
|
|
15
|
|
|
8
|
|
260
|
|
|
|
15
|
|
|
8
|
|
71
|
|
|
|
15
|
|
|
8
|
|
28
|
|
|
|
15
|
|
|
6
|
|
267
|
|
|
|
15
|
|
|
6
|
|
79
|
|
|
|
15
|
|
|
6
|
|
31
|
|
|
|
15
|
|
|
6
|
|
240
|
|
|
|
15
|
|
|
6
|
|
64
|
|
|
|
15
|
|
|
6
|
|
27
|
|
|
|
15
|
|
|
6
|
|
90
|
|
|
|
13
|
|
|
6
|
|
102
|
|
|
|
13
|
|
|
6
|
|
34
|
|
|
|
13
|
|
|
6
|
|
259
|
|
|
|
13
|
|
|
6
|
|
59
|
|
|
|
13
|
|
|
6
|
|
41
|
|
|
|
13
|
|
|
6
|
|
189
|
|
|
|
13
|
|
|
6
|
|
64
|
|
|
|
13
|
|
|
6
|
|
47
|
|
|
|
13
|
|
|
6
|
|
231
|
|
|
|
13
|
|
|
6
|
|
62
|
|
|
|
13
|
|
|
5
|
|
30
|
|
|
|
13
|
|
|
5
|
|
238
|
|
|
|
13
|
|
|
5
|
|
59
|
|
|
|
13
|
|
|
5
|
|
30
|
|
|
|
13
|
|
|
5
|
|
224
|
|
|
|
13
|
|
|
5
|
|
56
|
|
|
|
13
|
|
|
5
|
|
24
|
|
|
|
13
|
|
|
5
|
|
217
|
|
|
|
13
|
|
|
5
|
|
57
|
|
|
|
13
|
|
|
5
|
|
27
|
|
|
|
13
|
|
|
5
|
|
110
|
|
|
|
12
|
|
|
6
|
|
106
|
|
|
|
12
|
|
|
6
|
|
59
|
|
|
|
12
|
|
|
6
|
|
241
|
|
|
|
12
|
|
|
6
|
|
62
|
|
|
|
12
|
|
|
6
|
|
29
|
|
|
|
12
|
|
|
6
|
|
214
|
|
|
|
12
|
|
|
6
|
|
62
|
|
|
|
12
|
|
|
6
|
|
25
|
|
|
|
12
|
|
|
4
|
|
280
|
|
|
|
12
|
|
|
4
|
|
63
|
|
|
|
12
|
|
|
4
|
|
24
|
|
|
|
12
|
|
|
4
|
|
227
|
|
|
|
12
|
|
|
4
|
|
58
|
|
|
|
12
|
|
|
4
|
|
26
|
|
|
|
12
|
|
|
4
|
|
241
|
|
|
|
12
|
|
|
4
|
|
76
|
|
|
|
12
|
|
|
4
|
|
28
|
|
|
|
12
|
|
|
4
|
|
225
|
|
|
|
12
|
|
|
4
|
|
58
|
|
|
|
12
|
|
|
4
|
|
24
|
|
|
|
12
|
|
|
4
|
|
82
|
|
|
|
11
|
|
|
|
|
79
|
|
|
|
11
|
|
|
|
|
23
|
|
|
|
11
|
|
|
|
|
238
|
|
|
|
11
|
|
|
|
|
53
|
|
|
|
11
|
|
|
|
|
29
|
|
|
|
11
|
|
|
|
|
189
|
|
|
|
11
|
|
|
|
|
54
|
|
|
|
11
|
|
|
|
|
26
|
|
|
|
11
|
|
|
|
|
173
|
|
|
|
11
|
|
|
|
|
45
|
|
|
|
11
|
|
|
|
|
22
|
|
|
|
11
|
|
|
|
|
150
|
|
|
|
11
|
|
|
|
|
42
|
|
|
|
11
|
|
|
|
|
17
|
|
|
|
11
|
|
|
|
|
216
|
|
|
|
11
|
|
|
|
|
46
|
|
|
|
11
|
|
|
|
|
18
|
|
|
|
11
|
|
|
|
|
197
|
|
|
|
11
|
|
|
|
|
44
|
|
|
|
11
|
|
|
|
|
25
|
|
|
|
11
|
|
|
|
|
58
|
|
|
|
10
|
|
|
|
|
80
|
|
|
|
10
|
|
|
|
|
23
|
|
|
|
10
|
|
|
|
|
215
|
|
|
|
10
|
|
|
|
|
39
|
|
|
|
10
|
|
|
|
|
21
|
|
|
|
10
|
|
|
|
|
154
|
|
|
|
9
|
|
|
|
|
99
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
146
|
|
|
|
9
|
|
|
|
|
38
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
160
|
|
|
|
9
|
|
|
|
|
36
|
|
|
|
9
|
|
|
|
|
14
|
|
|
|
9
|
|
|
|
|
140
|
|
|
|
9
|
|
|
|
|
32
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
110
|
|
|
|
9
|
|
|
|
|
30
|
|
|
|
9
|
|
|
|
|
19
|
|
|
|
9
|
|
|
|
|
62
|
|
|
|
9
|
|
|
|
|
74
|
|
|
|
9
|
|
|
|
|
17
|
|
|
|
9
|
|
|
|
|
169
|
|
|
|
9
|
|
|
|
|
41
|
|
|
|
9
|
|
|
|
|
21
|
|
|
|
9
|
|
|
|
|
147
|
|
|
|
9
|
|
|
|
|
50
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
142
|
|
|
|
9
|
|
|
|
|
40
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
179
|
|
|
|
9
|
|
|
|
|
42
|
|
|
|
9
|
|
|
|
|
21
|
|
|
|
9
|
|
|
|
|
182
|
|
|
|
9
|
|
|
|
|
50
|
|
|
|
9
|
|
|
|
|
20
|
|
|
|
9
|
|
|
|
|
144
|
|
|
|
9
|
|
|
|
|
71
|
|
|
|
9
|
|
|
|
|
17
|
|
|
|
9
|
|
|
|
|
59
|
|
|
|
9
|
|
|
|
|
76
|
|
|
|
9
|
|
|
|
|
26
|
|
|
|
9
|
|
|
|
|
255
|
|
|
|
9
|
|
|
|
|
45
|
|
|
|
9
|
|
|
|
|
21
|
|
|
|
9
|
|
|
|
|
584
|
|
|
|
9
|
|
|
|
|
52
|
|
|
|
9
|
|
|
|
|
39
|
|
|
|
9
|
|
|
|
|
161
|
|
|
|
9
|
|
|
|
|
43
|
|
|
|
9
|
|
|
|
|
25
|
|
|
|
9
|
|
|
|
|
142
|
|
|
|
9
|
|
|
|
|
38
|
|
|
|
9
|
|
|
|
|
19
|
|
|
|
9
|
|
|
|
|
198
|
|
|
|
9
|
|
|
|
|
63
|
|
|
|
9
|
|
|
|
|
20
|
|
|
|
9
|
|
|
|
|
225
|
|
|
|
9
|
|
|
|
|
58
|
|
|
|
9
|
|
|
|
|
23
|
|
|
|
9
|
|
|
|
|
58
|
|
|
|
8
|
|
|
|
|
71
|
|
|
|
8
|
|
|
|
|
21
|
|
|
|
8
|
|
|
|
|
177
|
|
|
|
8
|
|
|
|
|
43
|
|
|
|
8
|
|
|
|
|
22
|
|
|
|
8
|
|
|
|
|
131
|
|
|
|
8
|
|
|
|
|
49
|
|
|
|
8
|
|
|
|
|
19
|
|
|
|
8
|
|
|
|
|
1381
|
|
|
|
8
|
|
|
|
|
57
|
|
|
|
8
|
|
|
|
|
25
|
|
|
|
8
|
|
|
|
|
189
|
|
|
|
8
|
|
|
|
|
44
|
|
|
|
8
|
|
|
|
|
18
|
|
|
|
8
|
|
|
|
|
152
|
|
|
|
8
|
|
|
|
|
40
|
|
|
|
8
|
|
|
|
|
19
|
|
|
|
8
|
|
|
|
|
133
|
|
|
|
8
|
|
|
|
|
41
|
|
|
|
8
|
|
|
|
|
30
|
|
|
|
8
|
|
|
|
|
75
|
|
|
|
6
|
|
|
|
|
49
|
|
|
|
6
|
|
|
|
|
17
|
|
|
|
6
|
|
|
|
|
104
|
|
|
|
6
|
|
|
|
|
29
|
|
|
|
6
|
|
|
|
|
17
|
|
|
|
6
|
|
|
|
|
125
|
|
|
|
6
|
|
|
|
|
38
|
|
|
|
6
|
|
|
|
|
14
|
|
|
|
6
|
|
|
|
|
121
|
|
|
|
6
|
|
|
|
|
28
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
88
|
|
|
|
6
|
|
|
|
|
28
|
|
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
157
|
|
|
|
6
|
|
|
|
|
40
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
159
|
|
|
|
6
|
|
|
|
|
44
|
|
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
45
|
|
|
|
6
|
|
|
|
|
50
|
|
|
|
6
|
|
|
|
|
14
|
|
|
|
6
|
|
|
|
|
121
|
|
|
|
6
|
|
|
|
|
27
|
|
|
|
6
|
|
|
|
|
16
|
|
|
|
6
|
|
|
|
|
112
|
|
|
|
6
|
|
|
|
|
38
|
|
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
109
|
|
|
|
6
|
|
|
|
|
30
|
|
|
|
6
|
|
|
|
|
11
|
|
|
|
6
|
|
|
|
|
97
|
|
|
|
6
|
|
|
|
|
30
|
|
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
102
|
|
|
|
6
|
|
|
|
|
28
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
147
|
|
|
|
6
|
|
|
|
|
33
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
60
|
|
|
|
6
|
|
|
|
|
710
|
|
|
|
6
|
|
|
|
|
4896
|
|
|
|
6
|
|
|
|
|
149
|
|
|
|
6
|
|
|
|
|
822
|
|
|
|
6
|
|
|
|
|
4644
|
|
|
|
6
|
|
|
|
|
127
|
|
|
|
6
|
|
|
|
|
690
|
|
|
|
6
|
|
|
|
|
4556
|
|
|
|
6
|
|
|
|
|
104
|
|
|
|
5
|
|
|
|
|
22
|
|
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
64
|
|
|
|
5
|
|
|
|
|
22
|
|
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
118
|
|
|
|
5
|
|
|
|
|
25
|
|
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
84
|
|
|
|
5
|
|
|
|
|
21
|
|
|
|
5
|
|
|
|
|
14
|
|
|
|
5
|
|
|
|
|
31
|
|
|
|
5
|
|
|
|
|
41
|
|
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
126
|
|
|
|
5
|
|
|
|
|
26
|
|
|
|
5
|
|
|
|
|
12
|
|
|
|
5
|
|
|
|
|
59
|
|
|
|
5
|
|
|
|
|
27
|
|
|
|
5
|
|
|
|
|
12
|
|
|
|
5
|
|
|
|
|
68
|
|
|
|
5
|
|
|
|
|
28
|
|
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
62
|
|
|
|
5
|
|
|
|
|
22
|
|
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
127
|
|
|
|
5
|
|
|
|
|
26
|
|
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
62
|
|
|
|
5
|
|
|
|
|
19
|
|
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
33
|
|
|
|
6
|
|
|
|
|
50
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
164
|
|
|
|
6
|
|
|
|
|
32
|
|
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
101
|
|
|
|
6
|
|
|
|
|
35
|
|
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
132
|
|
|
|
6
|
|
|
|
|
33
|
|
|
|
6
|
|
|
|
|
28
|
|
|
|
6
|
|
|
|
|
101
|
|
|
|
6
|
|
|
|
|
24
|
|
|
|
6
|
|
|
|
|
11
|
|
|
|
6
|
|
|
|
|
89
|
|
|
|
6
|
|
|
|
|
25
|
|
|
|
6
|
|
|
|
|
15
|
|
|
|
6
|
|
|
|
|
111
|
|
|
|
6
|
|
|
|
|
27
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
51
|
|
|
|
6
|
|
|
|
|
38
|
|
|
|
6
|
|
|
|
|
11
|
|
|
|
6
|
|
|
|
|
108
|
|
|
|
4
|
|
|
|
|
20
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
52
|
|
|
|
4
|
|
|
|
|
23
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
73
|
|
|
|
4
|
|
|
|
|
20
|
|
|
|
4
|
|
|
|
|
11
|
|
|
|
4
|
|
|
|
|
100
|
|
|
|
4
|
|
|
|
|
20
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
52
|
|
|
|
4
|
|
|
|
|
15
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
62
|
|
|
|
4
|
|
|
|
|
17
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
30
|
|
|
|
4
|
|
|
|
|
30
|
|
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
68
|
|
|
|
4
|
|
|
|
|
20
|
|
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
46
|
|
|
|
4
|
|
|
|
|
23
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
82
|
|
|
|
4
|
|
|
|
|
23
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
80
|
|
|
|
4
|
|
|
|
|
17
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
55
|
|
|
|
4
|
|
|
|
|
16
|
|
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
98
|
|
|
|
4
|
|
|
|
|
19
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
29
|
|
|
965
|
|
|
|
|
|
|
qw(Core Validation FormatAnnotation Applicator Content MetaData Unevaluated); |
|
966
|
|
|
|
|
|
|
+{ |
|
967
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/schema' => [ 'draft2020-12', [ @modules ] ], |
|
968
|
|
|
|
|
|
|
do { pop @modules; () }, # remove Unevaluated |
|
969
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/schema' => [ 'draft2019-09', [ @modules ] ], |
|
970
|
|
|
|
|
|
|
'http://json-schema.org/draft-07/schema' => [ 'draft7', [ @modules ] ], |
|
971
|
|
|
|
|
|
|
do { splice @modules, 4, 1; () }, # remove Content |
|
972
|
|
|
|
|
|
|
'http://json-schema.org/draft-06/schema' => [ 'draft6', \@modules ], |
|
973
|
|
|
|
|
|
|
'http://json-schema.org/draft-04/schema' => [ 'draft4', \@modules ], |
|
974
|
|
|
|
|
|
|
}, |
|
975
|
|
|
|
|
|
|
}, |
|
976
|
|
|
|
|
|
|
); |
|
977
|
|
|
|
|
|
|
|
|
978
|
30489
|
|
|
30490
|
|
986212
|
sub _get_metaschema_vocabulary_classes { $_[0]->__metaschema_vocabulary_classes->{$_[1] =~ s/#\z//r} } |
|
979
|
5924
|
|
|
5925
|
|
48796
|
sub _set_metaschema_vocabulary_classes { $_[0]->__metaschema_vocabulary_classes->{$_[1] =~ s/#\z//r} = $mvc_type->($_[2]) } |
|
980
|
4
|
|
|
5
|
|
322
|
sub __all_metaschema_vocabulary_classes { values $_[0]->__metaschema_vocabulary_classes->%* } |
|
981
|
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
# translate vocabulary URIs into classes, caching the results (if any) |
|
983
|
53
|
|
|
54
|
|
161
|
sub _fetch_vocabulary_data ($self, $state, $schema_info) { |
|
|
53
|
|
|
|
|
142
|
|
|
|
53
|
|
|
|
|
190
|
|
|
|
53
|
|
|
|
|
120
|
|
|
|
53
|
|
|
|
|
193
|
|
|
984
|
53
|
100
|
|
|
|
462
|
if (not exists $schema_info->{schema}{'$vocabulary'}) { |
|
985
|
|
|
|
|
|
|
# "If "$vocabulary" is absent, an implementation MAY determine behavior based on the meta-schema |
|
986
|
|
|
|
|
|
|
# if it is recognized from the URI value of the referring schema's "$schema" keyword." |
|
987
|
2
|
|
|
|
|
18
|
my $metaschema_uri = $self->METASCHEMA_URIS->{$schema_info->{specification_version}}; |
|
988
|
2
|
|
|
|
|
8
|
return $self->_get_metaschema_vocabulary_classes($metaschema_uri)->@*; |
|
989
|
|
|
|
|
|
|
} |
|
990
|
|
|
|
|
|
|
|
|
991
|
51
|
|
|
|
|
142
|
my $valid = 1; |
|
992
|
|
|
|
|
|
|
# Core §8.1.2-6: "The "$vocabulary" keyword SHOULD be used in the root schema of any schema |
|
993
|
|
|
|
|
|
|
# document intended for use as a meta-schema. It MUST NOT appear in subschemas." |
|
994
|
51
|
100
|
|
|
|
261
|
$valid = E($state, '$vocabulary can only appear at the document root') if length $schema_info->{document_path}; |
|
995
|
51
|
100
|
|
|
|
422
|
$valid = E($state, 'metaschemas must have an $id') if not exists $schema_info->{schema}{'$id'}; |
|
996
|
|
|
|
|
|
|
|
|
997
|
51
|
100
|
|
|
|
214
|
return (undef, []) if not $valid; |
|
998
|
|
|
|
|
|
|
|
|
999
|
49
|
|
|
|
|
121
|
my @vocabulary_classes; |
|
1000
|
|
|
|
|
|
|
|
|
1001
|
49
|
|
|
|
|
544
|
foreach my $uri (sort keys $schema_info->{schema}{'$vocabulary'}->%*) { |
|
1002
|
105
|
|
|
|
|
415
|
my $class_info = $self->_get_vocabulary_class($uri); |
|
1003
|
|
|
|
|
|
|
$valid = E({ %$state, _keyword_path_suffix => $uri }, '"%s" is not a known vocabulary', $uri), next |
|
1004
|
105
|
100
|
100
|
|
|
17783
|
if $schema_info->{schema}{'$vocabulary'}{$uri} and not $class_info; |
|
1005
|
|
|
|
|
|
|
|
|
1006
|
97
|
100
|
|
|
|
1414
|
next if not $class_info; # vocabulary is not known, but marked as false in the metaschema |
|
1007
|
|
|
|
|
|
|
|
|
1008
|
89
|
|
|
|
|
279
|
my ($spec_version, $class) = @$class_info; |
|
1009
|
|
|
|
|
|
|
$valid = E({ %$state, _keyword_path_suffix => $uri }, '"%s" uses %s, but the metaschema itself uses %s', |
|
1010
|
|
|
|
|
|
|
$uri, $spec_version, $schema_info->{specification_version}), next |
|
1011
|
89
|
100
|
|
|
|
492
|
if $spec_version ne $schema_info->{specification_version}; |
|
1012
|
|
|
|
|
|
|
|
|
1013
|
83
|
|
|
|
|
293
|
push @vocabulary_classes, $class; |
|
1014
|
|
|
|
|
|
|
} |
|
1015
|
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
@vocabulary_classes = sort { |
|
1017
|
49
|
50
|
|
|
|
298
|
$a->evaluation_order <=> $b->evaluation_order |
|
|
49
|
50
|
|
|
|
303
|
|
|
1018
|
|
|
|
|
|
|
|| ($a->evaluation_order == 999 ? 0 |
|
1019
|
|
|
|
|
|
|
: ($valid = E($state, '%s and %s have a conflicting evaluation_order', sort $a, $b))) |
|
1020
|
|
|
|
|
|
|
} @vocabulary_classes; |
|
1021
|
|
|
|
|
|
|
|
|
1022
|
49
|
100
|
100
|
|
|
360
|
$valid = E($state, 'the first vocabulary (by evaluation_order) must be Core') |
|
1023
|
|
|
|
|
|
|
if ($vocabulary_classes[0]//'') ne 'JSON::Schema::Modern::Vocabulary::Core'; |
|
1024
|
|
|
|
|
|
|
|
|
1025
|
49
|
|
|
|
|
134
|
my %seen_keyword; |
|
1026
|
49
|
|
|
|
|
168
|
foreach my $class (@vocabulary_classes) { |
|
1027
|
83
|
|
|
|
|
701
|
foreach my $keyword ($class->keywords($schema_info->{specification_version})) { |
|
1028
|
|
|
|
|
|
|
$valid = E($state, '%s keyword "%s" conflicts with keyword of the same name from %s', |
|
1029
|
|
|
|
|
|
|
$class, $keyword, $seen_keyword{$keyword}) |
|
1030
|
794
|
100
|
|
|
|
1727
|
if $seen_keyword{$keyword}; |
|
1031
|
794
|
|
|
|
|
2133
|
$seen_keyword{$keyword} = $class; |
|
1032
|
|
|
|
|
|
|
} |
|
1033
|
|
|
|
|
|
|
} |
|
1034
|
|
|
|
|
|
|
|
|
1035
|
49
|
100
|
|
|
|
932
|
return ($schema_info->{specification_version}, $valid ? \@vocabulary_classes : []); |
|
1036
|
|
|
|
|
|
|
} |
|
1037
|
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
# used for determining a default '$schema' keyword where there is none |
|
1039
|
|
|
|
|
|
|
# these are also normalized as this is how we cache them |
|
1040
|
47
|
|
|
|
|
8694
|
use constant METASCHEMA_URIS => { |
|
1041
|
|
|
|
|
|
|
'draft2020-12' => 'https://json-schema.org/draft/2020-12/schema', |
|
1042
|
|
|
|
|
|
|
'draft2019-09' => 'https://json-schema.org/draft/2019-09/schema', |
|
1043
|
|
|
|
|
|
|
'draft7' => 'http://json-schema.org/draft-07/schema', |
|
1044
|
|
|
|
|
|
|
'draft6' => 'http://json-schema.org/draft-06/schema', |
|
1045
|
|
|
|
|
|
|
'draft4' => 'http://json-schema.org/draft-04/schema', |
|
1046
|
47
|
|
|
47
|
|
97693
|
}; |
|
|
47
|
|
|
|
|
134
|
|
|
1047
|
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
# for internal use only. files are under share/ |
|
1049
|
47
|
|
|
|
|
75417
|
use constant _CACHED_METASCHEMAS => { |
|
1050
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/applicator' => 'draft2020-12/meta/applicator.json', |
|
1051
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/content' => 'draft2020-12/meta/content.json', |
|
1052
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/core' => 'draft2020-12/meta/core.json', |
|
1053
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/format-annotation' => 'draft2020-12/meta/format-annotation.json', |
|
1054
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/format-assertion' => 'draft2020-12/meta/format-assertion.json', |
|
1055
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/meta-data' => 'draft2020-12/meta/meta-data.json', |
|
1056
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/unevaluated' => 'draft2020-12/meta/unevaluated.json', |
|
1057
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/meta/validation' => 'draft2020-12/meta/validation.json', |
|
1058
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/output/schema' => 'draft2020-12/output/schema.json', |
|
1059
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/schema' => 'draft2020-12/schema.json', |
|
1060
|
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/meta/applicator' => 'draft2019-09/meta/applicator.json', |
|
1062
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/meta/content' => 'draft2019-09/meta/content.json', |
|
1063
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/meta/core' => 'draft2019-09/meta/core.json', |
|
1064
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/meta/format' => 'draft2019-09/meta/format.json', |
|
1065
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/meta/meta-data' => 'draft2019-09/meta/meta-data.json', |
|
1066
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/meta/validation' => 'draft2019-09/meta/validation.json', |
|
1067
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/output/schema' => 'draft2019-09/output/schema.json', |
|
1068
|
|
|
|
|
|
|
'https://json-schema.org/draft/2019-09/schema' => 'draft2019-09/schema.json', |
|
1069
|
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
# trailing # is omitted because we always cache documents by its canonical (fragmentless) URI |
|
1071
|
|
|
|
|
|
|
'http://json-schema.org/draft-07/schema' => 'draft7/schema.json', |
|
1072
|
|
|
|
|
|
|
'http://json-schema.org/draft-06/schema' => 'draft6/schema.json', |
|
1073
|
|
|
|
|
|
|
'http://json-schema.org/draft-04/schema' => 'draft4/schema.json', |
|
1074
|
47
|
|
|
47
|
|
422
|
}; |
|
|
47
|
|
|
|
|
126
|
|
|
1075
|
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
# simple runtime-wide cache of metaschema document objects that are sourced from disk |
|
1077
|
|
|
|
|
|
|
my $metaschema_cache = {}; |
|
1078
|
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
{ |
|
1080
|
|
|
|
|
|
|
my $share_dir = dist_dir('JSON-Schema-Modern'); |
|
1081
|
|
|
|
|
|
|
JSON::Schema::Modern::Utilities::register_schema($_, $share_dir.'/'._CACHED_METASCHEMAS->{$_}) |
|
1082
|
|
|
|
|
|
|
foreach keys _CACHED_METASCHEMAS->%*; |
|
1083
|
|
|
|
|
|
|
} |
|
1084
|
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
# returns the same as _get_resource |
|
1086
|
24412
|
|
|
24413
|
|
1453315
|
sub _get_or_load_resource ($self, $uri) { |
|
|
24412
|
|
|
|
|
53415
|
|
|
|
24412
|
|
|
|
|
51536
|
|
|
|
24412
|
|
|
|
|
42934
|
|
|
1087
|
24412
|
|
|
|
|
89726
|
my $resource = $self->_get_resource($uri); |
|
1088
|
24412
|
100
|
|
|
|
3439445
|
return $resource if $resource; |
|
1089
|
|
|
|
|
|
|
|
|
1090
|
114
|
100
|
|
|
|
681
|
if (my $document = load_cached_document($self, $uri)) { |
|
1091
|
103
|
|
|
|
|
533
|
return $self->_get_resource($uri); |
|
1092
|
|
|
|
|
|
|
} |
|
1093
|
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
# TODO: |
|
1095
|
|
|
|
|
|
|
# - load from network or disk |
|
1096
|
|
|
|
|
|
|
|
|
1097
|
11
|
|
|
|
|
33
|
return; |
|
1098
|
|
|
|
|
|
|
}; |
|
1099
|
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
# returns information necessary to use a schema found at a particular URI or uri-reference: |
|
1101
|
|
|
|
|
|
|
# - schema: a schema (which may not be at a document root) |
|
1102
|
|
|
|
|
|
|
# - canonical_uri: the canonical uri for that schema, |
|
1103
|
|
|
|
|
|
|
# - document: the JSON::Schema::Modern::Document object that holds that schema |
|
1104
|
|
|
|
|
|
|
# - document_path: the path relative to the document root for this schema |
|
1105
|
|
|
|
|
|
|
# - specification_version: the specification version that applies to this schema |
|
1106
|
|
|
|
|
|
|
# - vocabularies: the vocabularies to use when considering schema keywords |
|
1107
|
|
|
|
|
|
|
# creates a Document and adds it to the resource index, if not already present. |
|
1108
|
24174
|
|
|
24175
|
|
402018
|
sub _fetch_from_uri ($self, $uri_reference) { |
|
|
24174
|
|
|
|
|
50567
|
|
|
|
24174
|
|
|
|
|
64478
|
|
|
|
24174
|
|
|
|
|
43253
|
|
|
1109
|
24174
|
50
|
|
|
|
128515
|
$uri_reference = Mojo::URL->new($uri_reference) if not is_schema($uri_reference); |
|
1110
|
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
# this is *a* resource that would contain our desired location, but may not be the closest one |
|
1112
|
24174
|
|
|
|
|
8882732
|
my $resource = $self->_get_or_load_resource($uri_reference->clone->fragment(undef)); |
|
1113
|
24174
|
100
|
|
|
|
184609
|
return if not $resource; |
|
1114
|
|
|
|
|
|
|
|
|
1115
|
24163
|
|
|
|
|
74643
|
my $fragment = $uri_reference->fragment; |
|
1116
|
24163
|
100
|
100
|
|
|
191340
|
if (not length($fragment) or $fragment =~ m{^/}) { |
|
1117
|
23583
|
|
100
|
|
|
294457
|
my $subschema = $resource->{document}->get(my $document_path = $resource->{path}.($fragment//'')); |
|
1118
|
23583
|
100
|
|
|
|
270224
|
return if not defined $subschema; |
|
1119
|
|
|
|
|
|
|
|
|
1120
|
23581
|
|
|
|
|
49298
|
my $closest_resource; |
|
1121
|
23581
|
100
|
|
|
|
65510
|
if (not length $fragment) { # we already have the canonical resource root |
|
1122
|
21460
|
|
|
|
|
64309
|
$closest_resource = [ undef, $resource ]; |
|
1123
|
|
|
|
|
|
|
} |
|
1124
|
|
|
|
|
|
|
else { |
|
1125
|
|
|
|
|
|
|
# determine the canonical uri by finding the closest schema resource(s) |
|
1126
|
2121
|
|
|
|
|
8668
|
my $doc_addr = refaddr($resource->{document}); |
|
1127
|
|
|
|
|
|
|
my @closest_resources = |
|
1128
|
500
|
|
|
|
|
4316
|
sort { length($b->[1]{path}) <=> length($a->[1]{path}) } # sort by length, descending |
|
1129
|
|
|
|
|
|
|
grep { !length($_->[1]{path}) # document root |
|
1130
|
2909
|
100
|
66
|
|
|
36205
|
|| length($document_path) |
|
1131
|
|
|
|
|
|
|
&& $document_path =~ m{^\Q$_->[1]{path}\E(?:/|\z)} } # path is above desired location |
|
1132
|
2121
|
|
|
|
|
10456
|
grep { refaddr($_->[1]{document}) == $doc_addr } # in same document |
|
|
228326
|
|
|
|
|
536366
|
|
|
1133
|
|
|
|
|
|
|
$self->_resource_pairs; |
|
1134
|
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
# now whittle down to all the resources with the same document path as the first candidate |
|
1136
|
2121
|
100
|
|
|
|
62981
|
if (@closest_resources > 1) { |
|
1137
|
|
|
|
|
|
|
# find the resource key that most closely matches the original query uri, by matching prefixes |
|
1138
|
462
|
|
|
|
|
2932
|
my $match = $uri_reference.''; |
|
1139
|
|
|
|
|
|
|
@closest_resources = |
|
1140
|
24
|
|
|
|
|
117
|
sort { _prefix_match_length($b->[0], $match) <=> _prefix_match_length($a->[0], $match) } |
|
1141
|
|
|
|
|
|
|
grep $_->[1]{path} eq $closest_resources[0]->[1]{path}, |
|
1142
|
462
|
|
|
|
|
139289
|
@closest_resources; |
|
1143
|
|
|
|
|
|
|
} |
|
1144
|
|
|
|
|
|
|
|
|
1145
|
2121
|
|
|
|
|
6353
|
$closest_resource = $closest_resources[0]; |
|
1146
|
|
|
|
|
|
|
} |
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
my $canonical_uri = $closest_resource->[1]{canonical_uri}->clone |
|
1149
|
23581
|
|
|
|
|
150179
|
->fragment(substr($document_path, length($closest_resource->[1]{path}))); |
|
1150
|
23581
|
100
|
|
|
|
2430292
|
$canonical_uri->fragment(undef) if not length($canonical_uri->fragment); |
|
1151
|
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
return { |
|
1153
|
|
|
|
|
|
|
schema => $subschema, |
|
1154
|
|
|
|
|
|
|
canonical_uri => $canonical_uri, |
|
1155
|
|
|
|
|
|
|
document_path => $document_path, |
|
1156
|
23581
|
|
|
|
|
560553
|
$closest_resource->[1]->%{qw(document specification_version vocabularies)}, # reference, not copy |
|
1157
|
|
|
|
|
|
|
}; |
|
1158
|
|
|
|
|
|
|
} |
|
1159
|
|
|
|
|
|
|
else { # we are following a URI with a plain-name fragment |
|
1160
|
580
|
100
|
100
|
|
|
5799
|
return if not my $subresource = ($resource->{anchors}//{})->{$fragment}; |
|
1161
|
|
|
|
|
|
|
return { |
|
1162
|
|
|
|
|
|
|
schema => $resource->{document}->get($subresource->{path}), |
|
1163
|
|
|
|
|
|
|
canonical_uri => $subresource->{canonical_uri}, # this is *not* the anchor-containing URI |
|
1164
|
|
|
|
|
|
|
document_path => $subresource->{path}, |
|
1165
|
578
|
|
|
|
|
4460
|
$resource->%{qw(document specification_version vocabularies)}, # reference, not copy |
|
1166
|
|
|
|
|
|
|
}; |
|
1167
|
|
|
|
|
|
|
} |
|
1168
|
|
|
|
|
|
|
} |
|
1169
|
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# given two strings, determines the number of characters in common, starting from the first |
|
1171
|
|
|
|
|
|
|
# character |
|
1172
|
48
|
|
|
49
|
|
81
|
sub _prefix_match_length ($x, $y) { |
|
|
48
|
|
|
|
|
77
|
|
|
|
48
|
|
|
|
|
76
|
|
|
|
48
|
|
|
|
|
67
|
|
|
1173
|
48
|
|
|
|
|
151
|
my $len = min(length($x), length($y)); |
|
1174
|
48
|
|
|
|
|
135
|
foreach my $pos (0..$len) { |
|
1175
|
1406
|
100
|
|
|
|
2562
|
return $pos if substr($x, $pos, 1) ne substr($y, $pos, 1); |
|
1176
|
|
|
|
|
|
|
} |
|
1177
|
0
|
|
|
|
|
0
|
return $len; |
|
1178
|
|
|
|
|
|
|
} |
|
1179
|
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
# Mojo::JSON::JSON_XS is false when the environment variable $MOJO_NO_JSON_XS is set |
|
1181
|
|
|
|
|
|
|
# and also checks if Cpanel::JSON::XS is installed. |
|
1182
|
|
|
|
|
|
|
# Mojo::JSON falls back to its own pure-perl encoder/decoder but does not support all the options |
|
1183
|
|
|
|
|
|
|
# that we require here. |
|
1184
|
|
|
|
|
|
|
use constant _JSON_BACKEND => |
|
1185
|
47
|
|
|
|
|
970
|
Mojo::JSON::JSON_XS && eval { Cpanel::JSON::XS->VERSION('4.38'); 1 } ? 'Cpanel::JSON::XS' |
|
|
47
|
|
|
|
|
94182
|
|
|
1186
|
47
|
0
|
|
|
|
121
|
: eval { JSON::PP->VERSION('4.11'); 1 } ? 'JSON::PP' |
|
|
2
|
50
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
30
|
|
|
1187
|
47
|
|
|
47
|
|
705
|
: die 'Cpanel::JSON::XS 4.38 or JSON::PP 4.11 is required'; |
|
|
47
|
|
|
|
|
125
|
|
|
1188
|
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
# used for internal encoding as well (when caching serialized schemas) |
|
1190
|
|
|
|
|
|
|
has _json_decoder => ( |
|
1191
|
|
|
|
|
|
|
is => 'ro', |
|
1192
|
|
|
|
|
|
|
isa => HasMethods[qw(encode decode)], |
|
1193
|
|
|
|
|
|
|
lazy => 1, |
|
1194
|
|
|
|
|
|
|
default => sub { _JSON_BACKEND->new->allow_nonref(1)->canonical(1)->utf8(1)->allow_bignum(1)->convert_blessed(1) }, |
|
1195
|
|
|
|
|
|
|
); |
|
1196
|
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
# since media types are case-insensitive, all type names must be casefolded on insertion. |
|
1198
|
|
|
|
|
|
|
has _media_type => ( |
|
1199
|
|
|
|
|
|
|
is => 'bare', |
|
1200
|
|
|
|
|
|
|
isa => my $media_type_type = Map[Str->where(q{$_ eq CORE::fc($_)}), CodeRef], |
|
1201
|
|
|
|
|
|
|
reader => '__media_type', |
|
1202
|
|
|
|
|
|
|
lazy => 1, |
|
1203
|
|
|
|
|
|
|
default => sub ($self) { |
|
1204
|
|
|
|
|
|
|
my $_json_media_type = sub ($content_ref) { |
|
1205
|
|
|
|
|
|
|
# utf-8 decoding is always done, as per the JSON spec. |
|
1206
|
|
|
|
|
|
|
# other charsets are not supported: see RFC8259 §11 |
|
1207
|
|
|
|
|
|
|
\ _JSON_BACKEND->new->allow_nonref(1)->utf8(1)->decode($content_ref->$*); |
|
1208
|
|
|
|
|
|
|
}; |
|
1209
|
|
|
|
|
|
|
+{ |
|
1210
|
|
|
|
|
|
|
(map +($_ => $_json_media_type), |
|
1211
|
|
|
|
|
|
|
qw(application/json application/schema+json application/schema-instance+json)), |
|
1212
|
|
|
|
|
|
|
(map +($_ => sub ($content_ref) { $content_ref }), |
|
1213
|
|
|
|
|
|
|
qw(text/* application/octet-stream)), |
|
1214
|
|
|
|
|
|
|
'application/x-www-form-urlencoded' => sub ($content_ref) { |
|
1215
|
|
|
|
|
|
|
\ Mojo::Parameters->new->charset('UTF-8')->parse($content_ref->$*)->to_hash; |
|
1216
|
|
|
|
|
|
|
}, |
|
1217
|
|
|
|
|
|
|
'application/x-ndjson' => sub ($content_ref) { |
|
1218
|
|
|
|
|
|
|
my $decoder = _JSON_BACKEND->new->allow_nonref(1)->utf8(1); |
|
1219
|
|
|
|
|
|
|
my $line = 0; # line numbers start at 1 |
|
1220
|
|
|
|
|
|
|
\[ map { |
|
1221
|
|
|
|
|
|
|
do { |
|
1222
|
|
|
|
|
|
|
try { ++$line; $decoder->decode($_) } |
|
1223
|
|
|
|
|
|
|
catch ($e) { die 'parse error at line '.$line.': '.$e } |
|
1224
|
|
|
|
|
|
|
} |
|
1225
|
|
|
|
|
|
|
} |
|
1226
|
|
|
|
|
|
|
split(/\r?\n/, $content_ref->$*) |
|
1227
|
|
|
|
|
|
|
]; |
|
1228
|
|
|
|
|
|
|
}, |
|
1229
|
|
|
|
|
|
|
}; |
|
1230
|
|
|
|
|
|
|
}, |
|
1231
|
|
|
|
|
|
|
); |
|
1232
|
|
|
|
|
|
|
|
|
1233
|
5
|
|
|
6
|
1
|
2912
|
sub add_media_type { $media_type_type->({ @_[1..2] }); $_[0]->__media_type->{$_[1]} = $_[2]; } |
|
|
4
|
|
|
|
|
297
|
|
|
1234
|
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
# get_media_type('TExT/bloop') will fall through to matching an entry for 'text/*' or '*/*' |
|
1236
|
38
|
|
|
39
|
1
|
11106
|
sub get_media_type ($self, $type) { |
|
|
38
|
|
|
|
|
96
|
|
|
|
38
|
|
|
|
|
116
|
|
|
|
38
|
|
|
|
|
65
|
|
|
1237
|
38
|
|
|
|
|
1154
|
my $types = $self->__media_type; |
|
1238
|
38
|
|
|
|
|
1058
|
my $mt = $types->{fc $type}; |
|
1239
|
38
|
100
|
|
|
|
194
|
return $mt if $mt; |
|
1240
|
|
|
|
|
|
|
|
|
1241
|
9
|
100
|
100
|
42
|
|
69
|
return $types->{(first { m{([^/]+)/\*\z} && fc($type) =~ m{^\Q$1\E/[^/]+\z} } keys %$types) // '*/*'}; |
|
|
41
|
|
|
|
|
372
|
|
|
1242
|
|
|
|
|
|
|
}; |
|
1243
|
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
has _encoding => ( |
|
1245
|
|
|
|
|
|
|
is => 'bare', |
|
1246
|
|
|
|
|
|
|
isa => HashRef[CodeRef], |
|
1247
|
|
|
|
|
|
|
reader => '__encoding', |
|
1248
|
|
|
|
|
|
|
lazy => 1, |
|
1249
|
|
|
|
|
|
|
default => sub ($self) { |
|
1250
|
|
|
|
|
|
|
+{ |
|
1251
|
|
|
|
|
|
|
identity => sub ($content_ref) { $content_ref }, |
|
1252
|
|
|
|
|
|
|
base64 => sub ($content_ref) { |
|
1253
|
|
|
|
|
|
|
die "invalid characters\n" |
|
1254
|
|
|
|
|
|
|
if $content_ref->$* =~ m{[^A-Za-z0-9+/=]} or $content_ref->$* =~ m{=(?=[^=])}; |
|
1255
|
|
|
|
|
|
|
require MIME::Base64; \ MIME::Base64::decode_base64($content_ref->$*); |
|
1256
|
|
|
|
|
|
|
}, |
|
1257
|
|
|
|
|
|
|
base64url => sub ($content_ref) { |
|
1258
|
|
|
|
|
|
|
die "invalid characters\n" |
|
1259
|
|
|
|
|
|
|
if $content_ref->$* =~ m{[^A-Za-z0-9=_-]} or $content_ref->$* =~ m{=(?=[^=])}; |
|
1260
|
|
|
|
|
|
|
require MIME::Base64; \ MIME::Base64::decode_base64url($content_ref->$*); |
|
1261
|
|
|
|
|
|
|
}, |
|
1262
|
|
|
|
|
|
|
}; |
|
1263
|
|
|
|
|
|
|
}, |
|
1264
|
|
|
|
|
|
|
); |
|
1265
|
|
|
|
|
|
|
|
|
1266
|
22
|
|
|
23
|
1
|
938
|
sub get_encoding { $_[0]->__encoding->{$_[1]} } |
|
1267
|
0
|
|
|
1
|
1
|
0
|
sub add_encoding { $_[0]->__encoding->{$_[1]} = CodeRef->($_[2]) } |
|
1268
|
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
# callback hook for Sereal::Encoder |
|
1270
|
3
|
|
|
4
|
0
|
1853
|
sub FREEZE ($self, $serializer) { |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
8
|
|
|
|
3
|
|
|
|
|
4
|
|
|
1271
|
3
|
|
|
|
|
49
|
my $data = +{ %$self }; |
|
1272
|
|
|
|
|
|
|
# Cpanel::JSON::XS doesn't serialize: https://github.com/Sereal/Sereal/issues/266 |
|
1273
|
|
|
|
|
|
|
# coderefs can't serialize cleanly and must be re-added by the user. |
|
1274
|
3
|
|
|
|
|
21
|
delete $data->@{qw(_json_decoder _format_validations _media_type _encoding)}; |
|
1275
|
3
|
|
|
|
|
109
|
return $data; |
|
1276
|
|
|
|
|
|
|
} |
|
1277
|
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
# callback hook for Sereal::Decoder |
|
1279
|
4
|
|
|
5
|
0
|
1397
|
sub THAW ($class, $serializer, $data) { |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
11
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
7
|
|
|
1280
|
4
|
|
|
|
|
11
|
my $self = bless($data, $class); |
|
1281
|
|
|
|
|
|
|
|
|
1282
|
|
|
|
|
|
|
# load all vocabulary classes, both those used by loaded schemas, as well as all the core modules |
|
1283
|
|
|
|
|
|
|
load_module($_) |
|
1284
|
4
|
|
|
|
|
26
|
foreach uniq( |
|
1285
|
|
|
|
|
|
|
(map $_->{vocabularies}->@*, $self->_canonical_resources), |
|
1286
|
|
|
|
|
|
|
(map $_->[1], values $self->__vocabulary_classes->%*)); |
|
1287
|
|
|
|
|
|
|
|
|
1288
|
4
|
|
|
|
|
124
|
return $self; |
|
1289
|
|
|
|
|
|
|
} |
|
1290
|
|
|
|
|
|
|
|
|
1291
|
|
|
|
|
|
|
1; |
|
1292
|
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
__END__ |