| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
17
|
|
|
17
|
|
9232705
|
use strictures 2; |
|
|
17
|
|
|
|
|
161
|
|
|
|
17
|
|
|
|
|
745
|
|
|
2
|
|
|
|
|
|
|
package JSON::Schema::Modern::Document::OpenAPI; |
|
3
|
|
|
|
|
|
|
# vim: set ts=8 sts=2 sw=2 tw=100 et : |
|
4
|
|
|
|
|
|
|
# ABSTRACT: One OpenAPI v3.0, v3.1 or v3.2 document |
|
5
|
|
|
|
|
|
|
# KEYWORDS: JSON Schema data validation request response OpenAPI |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.139'; |
|
8
|
|
|
|
|
|
|
|
|
9
|
17
|
|
|
17
|
|
7318
|
use 5.020; |
|
|
17
|
|
|
|
|
62
|
|
|
10
|
17
|
|
|
17
|
|
70
|
use utf8; |
|
|
17
|
|
|
|
|
27
|
|
|
|
17
|
|
|
|
|
117
|
|
|
11
|
17
|
|
|
17
|
|
9624
|
use Moo; |
|
|
17
|
|
|
|
|
104439
|
|
|
|
17
|
|
|
|
|
80
|
|
|
12
|
17
|
|
|
17
|
|
22351
|
use strictures 2; |
|
|
17
|
|
|
|
|
114
|
|
|
|
17
|
|
|
|
|
632
|
|
|
13
|
17
|
|
|
17
|
|
7284
|
use stable 0.031 'postderef'; |
|
|
17
|
|
|
|
|
306
|
|
|
|
17
|
|
|
|
|
145
|
|
|
14
|
17
|
|
|
17
|
|
3217
|
use experimental 'signatures'; |
|
|
17
|
|
|
|
|
29
|
|
|
|
17
|
|
|
|
|
45
|
|
|
15
|
17
|
|
|
17
|
|
846
|
no autovivification warn => qw(fetch store exists delete); |
|
|
17
|
|
|
|
|
68
|
|
|
|
17
|
|
|
|
|
111
|
|
|
16
|
17
|
|
|
17
|
|
1132
|
use if "$]" >= 5.022, experimental => 're_strict'; |
|
|
17
|
|
|
|
|
28
|
|
|
|
17
|
|
|
|
|
405
|
|
|
17
|
17
|
|
|
17
|
|
1247
|
no if "$]" >= 5.031009, feature => 'indirect'; |
|
|
17
|
|
|
|
|
27
|
|
|
|
17
|
|
|
|
|
990
|
|
|
18
|
17
|
|
|
17
|
|
66
|
no if "$]" >= 5.033001, feature => 'multidimensional'; |
|
|
17
|
|
|
|
|
49
|
|
|
|
17
|
|
|
|
|
793
|
|
|
19
|
17
|
|
|
17
|
|
81
|
no if "$]" >= 5.033006, feature => 'bareword_filehandles'; |
|
|
17
|
|
|
|
|
29
|
|
|
|
17
|
|
|
|
|
750
|
|
|
20
|
17
|
|
|
17
|
|
65
|
no if "$]" >= 5.041009, feature => 'smartmatch'; |
|
|
17
|
|
|
|
|
45
|
|
|
|
17
|
|
|
|
|
609
|
|
|
21
|
17
|
|
|
17
|
|
75
|
no feature 'switch'; |
|
|
17
|
|
|
|
|
29
|
|
|
|
17
|
|
|
|
|
741
|
|
|
22
|
17
|
|
|
17
|
|
10075
|
use JSON::Schema::Modern::Utilities 0.625 qw(E canonical_uri jsonp unjsonp is_equal json_pointer_type assert_keyword_type assert_uri_reference load_cached_document get_type); |
|
|
17
|
|
|
|
|
2286788
|
|
|
|
17
|
|
|
|
|
2544
|
|
|
23
|
17
|
|
|
17
|
|
9465
|
use JSON::Schema::Modern::Result 0.630; |
|
|
17
|
|
|
|
|
2845565
|
|
|
|
17
|
|
|
|
|
763
|
|
|
24
|
17
|
|
|
17
|
|
9102
|
use OpenAPI::Modern::Utilities qw(:constants add_vocab_and_default_schemas); |
|
|
17
|
|
|
|
|
57
|
|
|
|
17
|
|
|
|
|
3046
|
|
|
25
|
17
|
|
|
17
|
|
112
|
use Carp qw(croak carp); |
|
|
17
|
|
|
|
|
27
|
|
|
|
17
|
|
|
|
|
847
|
|
|
26
|
17
|
|
|
17
|
|
79
|
use Digest::MD5 'md5_hex'; |
|
|
17
|
|
|
|
|
25
|
|
|
|
17
|
|
|
|
|
617
|
|
|
27
|
17
|
|
|
17
|
|
65
|
use Clone 'clone'; |
|
|
17
|
|
|
|
|
31
|
|
|
|
17
|
|
|
|
|
615
|
|
|
28
|
17
|
|
|
17
|
|
62
|
use builtin::compat qw(blessed indexed); |
|
|
17
|
|
|
|
|
24
|
|
|
|
17
|
|
|
|
|
116
|
|
|
29
|
17
|
|
|
17
|
|
2466
|
use MooX::TypeTiny 0.002002; |
|
|
17
|
|
|
|
|
363
|
|
|
|
17
|
|
|
|
|
133
|
|
|
30
|
17
|
|
|
17
|
|
11927
|
use Types::Standard qw(HashRef ArrayRef Str Map Any); |
|
|
17
|
|
|
|
|
31
|
|
|
|
17
|
|
|
|
|
158
|
|
|
31
|
17
|
|
|
17
|
|
45228
|
use namespace::clean; |
|
|
17
|
|
|
|
|
36
|
|
|
|
17
|
|
|
|
|
93
|
|
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
extends 'JSON::Schema::Modern::Document'; |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
our @CARP_NOT = qw(Sereal Sereal::Decoder JSON::Schema::Modern::Document); |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
has '+schema' => ( |
|
38
|
|
|
|
|
|
|
isa => HashRef, |
|
39
|
|
|
|
|
|
|
); |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# json pointer => entity name (indexed by integer); overrides parent |
|
42
|
|
|
|
|
|
|
# these aren't all the different types of objects; for now we only track those that are the valid |
|
43
|
|
|
|
|
|
|
# target of a $ref keyword in an openapi document. |
|
44
|
8396
|
|
|
8396
|
|
5769883
|
sub __entities { qw(schema response parameter example request-body header security-scheme link callbacks path-item media-type) } |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# operationId => document path |
|
47
|
|
|
|
|
|
|
has _operationIds => ( |
|
48
|
|
|
|
|
|
|
is => 'ro', |
|
49
|
|
|
|
|
|
|
isa => HashRef[json_pointer_type], |
|
50
|
|
|
|
|
|
|
lazy => 1, |
|
51
|
|
|
|
|
|
|
default => sub { {} }, |
|
52
|
|
|
|
|
|
|
); |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
*get_operationId_path = \&operationId_path; # deprecated |
|
55
|
|
|
|
|
|
|
|
|
56
|
169
|
|
|
169
|
1
|
2721
|
sub operationId_path { $_[0]->_operationIds->{$_[1]} } |
|
57
|
82
|
|
|
82
|
|
349
|
sub _add_operationId { $_[0]->_operationIds->{$_[1]} = json_pointer_type->($_[2]) } |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# tag name => document path of tag object |
|
60
|
|
|
|
|
|
|
has _tags => ( |
|
61
|
|
|
|
|
|
|
is => 'bare', |
|
62
|
|
|
|
|
|
|
isa => HashRef[json_pointer_type], |
|
63
|
|
|
|
|
|
|
lazy => 1, |
|
64
|
|
|
|
|
|
|
default => sub { {} }, |
|
65
|
|
|
|
|
|
|
); |
|
66
|
|
|
|
|
|
|
|
|
67
|
5
|
|
|
5
|
1
|
1951
|
sub tag_path { $_[0]->{_tags}{$_[1]} } |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# tag name => document path of operation |
|
70
|
|
|
|
|
|
|
has _operation_tags => ( |
|
71
|
|
|
|
|
|
|
is => 'bare', |
|
72
|
|
|
|
|
|
|
isa => HashRef[ArrayRef[json_pointer_type]], |
|
73
|
|
|
|
|
|
|
lazy => 1, |
|
74
|
|
|
|
|
|
|
default => sub { {} }, |
|
75
|
|
|
|
|
|
|
); |
|
76
|
|
|
|
|
|
|
|
|
77
|
6
|
|
100
|
6
|
1
|
44
|
sub operations_with_tag { ($_[0]->{_operation_tags}{$_[1]}//[])->@* } |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# the minor.major version of the OpenAPI specification used for this document |
|
80
|
|
|
|
|
|
|
has oas_version => ( |
|
81
|
|
|
|
|
|
|
is => 'rwp', |
|
82
|
|
|
|
|
|
|
isa => Str->where(q{/^[1-9]\.(?:0|[1-9][0-9]*)\z/}), |
|
83
|
|
|
|
|
|
|
); |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# list of /paths/* path templates, in canonical search order |
|
86
|
|
|
|
|
|
|
has path_templates => ( |
|
87
|
|
|
|
|
|
|
is => 'rwp', |
|
88
|
|
|
|
|
|
|
isa => ArrayRef[Str], |
|
89
|
|
|
|
|
|
|
); |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
has defaults => ( |
|
92
|
|
|
|
|
|
|
is => 'rwp', |
|
93
|
|
|
|
|
|
|
isa => Map[json_pointer_type, Any], |
|
94
|
|
|
|
|
|
|
lazy => 1, |
|
95
|
|
|
|
|
|
|
default => sub { {} }, |
|
96
|
|
|
|
|
|
|
); |
|
97
|
|
|
|
|
|
|
|
|
98
|
1
|
|
|
1
|
1
|
2120
|
sub default { $_[0]->defaults->{$_[1]} } |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# we define the sub directly, rather than using an 'around', since our root base class is not |
|
101
|
|
|
|
|
|
|
# Moo::Object, so we never got a BUILDARGS to modify |
|
102
|
398
|
|
|
398
|
0
|
3390328
|
sub BUILDARGS ($class, @args) { |
|
|
398
|
|
|
|
|
805
|
|
|
|
398
|
|
|
|
|
1149
|
|
|
|
398
|
|
|
|
|
725
|
|
|
103
|
398
|
|
|
|
|
2030
|
my $args = $class->Moo::Object::BUILDARGS(@args); # we do not inherit from Moo::Object |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
carp 'json_schema_dialect has been removed as a constructor attribute: use jsonSchemaDialect in your document instead' |
|
106
|
398
|
100
|
|
|
|
4352
|
if exists $args->{json_schema_dialect}; |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
carp 'specification_version argument is ignored by this subclass: use jsonSchemaDialect in your document instead' |
|
109
|
398
|
100
|
|
|
|
1854
|
if defined(delete($args->{specification_version})); |
|
110
|
|
|
|
|
|
|
|
|
111
|
398
|
|
|
|
|
5337
|
return $args; |
|
112
|
|
|
|
|
|
|
} |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# (probably) temporary, until the parent class evaluator is completely removed |
|
115
|
0
|
|
|
0
|
1
|
0
|
sub evaluator { die 'improper attempt to use of document evaluator' } |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# called by this class's base class constructor, in order to validate the integrity of the document |
|
118
|
|
|
|
|
|
|
# and identify all important details about this document, such as entity locations, referenceable |
|
119
|
|
|
|
|
|
|
# identifiers, operationIds, etc. |
|
120
|
396
|
|
|
396
|
0
|
131246
|
sub traverse ($self, $evaluator, $config_override = {}) { |
|
|
396
|
|
|
|
|
707
|
|
|
|
396
|
|
|
|
|
766
|
|
|
|
396
|
|
|
|
|
726
|
|
|
|
396
|
|
|
|
|
687
|
|
|
121
|
396
|
50
|
|
|
|
1963
|
croak join(', ', sort keys %$config_override), ' not supported as a config override in traverse' |
|
122
|
|
|
|
|
|
|
if keys %$config_override; |
|
123
|
|
|
|
|
|
|
|
|
124
|
396
|
|
|
|
|
4908
|
my $state = { |
|
125
|
|
|
|
|
|
|
initial_schema_uri => $self->canonical_uri, |
|
126
|
|
|
|
|
|
|
traversed_keyword_path => '', |
|
127
|
|
|
|
|
|
|
keyword_path => '', |
|
128
|
|
|
|
|
|
|
data_path => '', |
|
129
|
|
|
|
|
|
|
errors => [], |
|
130
|
|
|
|
|
|
|
evaluator => $evaluator, |
|
131
|
|
|
|
|
|
|
identifiers => {}, |
|
132
|
|
|
|
|
|
|
# note that this is the JSON Schema specification version, not OpenAPI version |
|
133
|
|
|
|
|
|
|
specification_version => $evaluator->SPECIFICATION_VERSION_DEFAULT, |
|
134
|
|
|
|
|
|
|
vocabularies => [], |
|
135
|
|
|
|
|
|
|
subschemas => [], |
|
136
|
|
|
|
|
|
|
references => [], |
|
137
|
|
|
|
|
|
|
depth => 0, |
|
138
|
|
|
|
|
|
|
traverse => 1, |
|
139
|
|
|
|
|
|
|
}; |
|
140
|
|
|
|
|
|
|
|
|
141
|
396
|
|
|
|
|
15853
|
my $schema = $self->schema; |
|
142
|
|
|
|
|
|
|
|
|
143
|
396
|
100
|
|
|
|
2327
|
()= E($state, 'missing openapi version'), return $state if not exists $schema->{openapi}; |
|
144
|
|
|
|
|
|
|
()= E($state, 'bad openapi version: "%s"', $schema->{openapi}//''), return $state |
|
145
|
395
|
100
|
100
|
|
|
4049
|
if ($schema->{openapi}//'') !~ /^\d+\.\d+\.\d+(-.+)?\z/a; |
|
|
|
|
100
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
|
147
|
393
|
|
|
|
|
2715
|
my @oad_version = split /[.-]/, $schema->{openapi}; |
|
148
|
393
|
|
|
|
|
7591
|
$self->_set_oas_version(join('.', @oad_version[0..1])); |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
my ($max_supported) = grep { |
|
151
|
393
|
|
|
|
|
7630
|
my @supported = split /\./; |
|
|
1179
|
|
|
|
|
2525
|
|
|
152
|
1179
|
100
|
|
|
|
4990
|
$supported[0] == $oad_version[0] && $supported[1] == $oad_version[1] |
|
153
|
|
|
|
|
|
|
} reverse SUPPORTED_OAD_VERSIONS->@*; |
|
154
|
|
|
|
|
|
|
|
|
155
|
393
|
100
|
|
|
|
1139
|
()= E($state, 'unrecognized/unsupported openapi version: "%s"', $schema->{openapi}), return $state |
|
156
|
|
|
|
|
|
|
if not defined $max_supported; |
|
157
|
|
|
|
|
|
|
carp 'WARNING: your document was written for version ', $schema->{openapi}, |
|
158
|
389
|
100
|
66
|
|
|
10137
|
' but this implementation has only been tested up to ', $max_supported, |
|
159
|
|
|
|
|
|
|
': this may be okay but you should upgrade your OpenAPI::Modern installation soon',"\n" |
|
160
|
|
|
|
|
|
|
if defined $oad_version[2] and (split(/\./, $max_supported))[2] < $oad_version[2]; |
|
161
|
|
|
|
|
|
|
|
|
162
|
389
|
|
|
|
|
2994
|
add_vocab_and_default_schemas($evaluator, $self->oas_version); |
|
163
|
|
|
|
|
|
|
|
|
164
|
389
|
100
|
|
|
|
761941
|
if (exists $schema->{'$self'}) { |
|
165
|
8
|
|
|
|
|
72
|
my $state = { %$state, keyword => '$self', initial_schema_uri => Mojo::URL->new }; |
|
166
|
|
|
|
|
|
|
|
|
167
|
8
|
100
|
66
|
|
|
202
|
if ($oad_version[0] == 3 and $oad_version[1] < 2) { |
|
168
|
1
|
|
|
|
|
5
|
()= E($state, 'additional property not permitted'); |
|
169
|
1
|
|
|
|
|
230
|
return $state; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
return $state |
|
173
|
|
|
|
|
|
|
if not assert_keyword_type($state, $schema, 'string') |
|
174
|
|
|
|
|
|
|
or not assert_uri_reference($state, $schema) |
|
175
|
7
|
100
|
66
|
|
|
37
|
or not ($schema->{'$self'} !~ /#/ || E($state, '$self cannot contain a fragment')); |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# determine canonical uri using rules from v3.2.0 ยง4.1.2.2.1, "Establishing the Base URI" |
|
179
|
|
|
|
|
|
|
$self->_set_canonical_uri($state->{initial_schema_uri} = |
|
180
|
386
|
|
66
|
|
|
5413
|
Mojo::URL->new($schema->{'$self'}//())->to_abs($self->retrieval_uri)); |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# /jsonSchemaDialect: https://spec.openapis.org/oas/latest#specifying-schema-dialects |
|
183
|
|
|
|
|
|
|
{ |
|
184
|
386
|
100
|
|
|
|
129427
|
if (exists $schema->{jsonSchemaDialect}) { |
|
|
386
|
|
|
|
|
2040
|
|
|
185
|
11
|
|
|
|
|
134
|
my $state = { %$state, keyword => 'jsonSchemaDialect' }; |
|
186
|
11
|
100
|
100
|
|
|
77
|
return $state |
|
187
|
|
|
|
|
|
|
if not assert_keyword_type($state, $schema, 'string') |
|
188
|
|
|
|
|
|
|
or not assert_uri_reference($state, $schema); |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# v3.2.0 ยง4.24.7, "Specifying Schema Dialects": "If [jsonSchemaDialect] is not set, then the OAS |
|
192
|
|
|
|
|
|
|
# dialect schema id MUST be used for these Schema Objects." |
|
193
|
|
|
|
|
|
|
# v3.2.0 ยง4.1.2.2, "Relative References in API Description URIs": "Unless specified otherwise, |
|
194
|
|
|
|
|
|
|
# all fields that are URIs MAY be relative references as defined by RFC3986 Section 4.2." |
|
195
|
|
|
|
|
|
|
my $json_schema_dialect = exists $schema->{jsonSchemaDialect} |
|
196
|
|
|
|
|
|
|
? Mojo::URL->new($schema->{jsonSchemaDialect})->to_abs($self->canonical_uri) |
|
197
|
384
|
100
|
|
|
|
6094
|
: DEFAULT_DIALECT->{$self->oas_version}; |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# continue to support the old strict dialect and metaschema which didn't have "3.1" in the $id |
|
200
|
384
|
100
|
|
|
|
4602
|
if ($json_schema_dialect eq (STRICT_DIALECT->{3.1} =~ s{/3.1/}{/}r)) { |
|
201
|
1
|
|
|
|
|
118
|
$json_schema_dialect =~ s{share/\K}{3.1/}; |
|
202
|
1
|
|
|
|
|
202
|
$schema->{jsonSchemaDialect} = $json_schema_dialect; # allow the 'const' check to pass |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
$self->_set_metaschema_uri($self->metaschema_uri =~ s{share/\K}{3.1/}r) |
|
205
|
384
|
100
|
100
|
|
|
3137
|
if $self->_has_metaschema_uri and $self->metaschema_uri eq (STRICT_METASCHEMA->{3.1} =~ s{/3.1/}{/}r); |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# we used to always preload these, so we need to do it as needed for users who are using them |
|
208
|
|
|
|
|
|
|
load_cached_document($evaluator, STRICT_DIALECT->{$self->oas_version}) |
|
209
|
|
|
|
|
|
|
if $self->_has_metaschema_uri and $self->metaschema_uri eq (STRICT_METASCHEMA->{$self->oas_version}//'') |
|
210
|
384
|
100
|
50
|
|
|
5490
|
or $json_schema_dialect eq (STRICT_DIALECT->{$self->oas_version}//''); |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
211
|
|
|
|
|
|
|
|
|
212
|
384
|
100
|
66
|
|
|
47286
|
if ($json_schema_dialect eq DEFAULT_DIALECT->{'3.0'} |
|
213
|
|
|
|
|
|
|
or $json_schema_dialect eq (DEFAULT_DIALECT->{'3.0'} =~ s/\b\d{4}-\d{2}-\d{2}\b/latest/r)) { |
|
214
|
24
|
50
|
|
|
|
90
|
croak '3.0 dialect with a non-3.0 OAD is not currently supported' if $self->oas_version ne '3.0'; |
|
215
|
|
|
|
|
|
|
|
|
216
|
24
|
|
|
|
|
112
|
$evaluator->add_vocabulary('JSON::Schema::Modern::Vocabulary::OpenAPI_3_0'); |
|
217
|
|
|
|
|
|
|
$evaluator->_set_metaschema_vocabulary_classes($json_schema_dialect => [ |
|
218
|
24
|
|
|
|
|
245
|
$state->@{qw(specification_version vocabularies)} = |
|
219
|
|
|
|
|
|
|
('draft4', ['JSON::Schema::Modern::Vocabulary::OpenAPI_3_0']) |
|
220
|
|
|
|
|
|
|
]); |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
else { |
|
223
|
|
|
|
|
|
|
# traverse an empty schema with this dialect uri to confirm it is valid, and add an entry in |
|
224
|
|
|
|
|
|
|
# the evaluator's _metaschema_vocabulary_classes |
|
225
|
360
|
|
|
|
|
7212
|
my $check_metaschema_state = $evaluator->traverse({}, { |
|
226
|
|
|
|
|
|
|
metaschema_uri => $json_schema_dialect, |
|
227
|
|
|
|
|
|
|
initial_schema_uri => $self->canonical_uri->clone->fragment('/jsonSchemaDialect'), |
|
228
|
|
|
|
|
|
|
traversed_keyword_path => '/jsonSchemaDialect', |
|
229
|
|
|
|
|
|
|
}); |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
# we cannot continue if the metaschema is invalid |
|
232
|
360
|
100
|
|
|
|
158798
|
if ($check_metaschema_state->{errors}->@*) { |
|
233
|
2
|
|
|
|
|
10
|
push $state->{errors}->@*, $check_metaschema_state->{errors}->@*; |
|
234
|
2
|
|
|
|
|
22
|
return $state; |
|
235
|
|
|
|
|
|
|
} |
|
236
|
|
|
|
|
|
|
|
|
237
|
358
|
|
|
|
|
3371
|
$state->@{qw(specification_version vocabularies)} = $check_metaschema_state->@{qw(specification_version vocabularies)}; |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# subsequent '$schema' keywords can still override this |
|
241
|
382
|
|
|
|
|
22097
|
$state->{json_schema_dialect} = $json_schema_dialect; |
|
242
|
|
|
|
|
|
|
|
|
243
|
382
|
100
|
|
|
|
9146
|
$self->_set_metaschema_uri(DEFAULT_METASCHEMA->{$self->oas_version}) |
|
244
|
|
|
|
|
|
|
if not $self->_has_metaschema_uri; |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
load_cached_document($evaluator, STRICT_METASCHEMA->{$self->oas_version}) |
|
247
|
382
|
100
|
100
|
|
|
45221
|
if $self->_has_metaschema_uri and $self->metaschema_uri eq (STRICT_METASCHEMA->{$self->oas_version}//''); |
|
|
|
|
66
|
|
|
|
|
|
248
|
|
|
|
|
|
|
} |
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
$state->{identifiers}{$state->{initial_schema_uri}} = { |
|
251
|
|
|
|
|
|
|
path => '', |
|
252
|
|
|
|
|
|
|
canonical_uri => $state->{initial_schema_uri}, |
|
253
|
|
|
|
|
|
|
specification_version => $state->{specification_version}, |
|
254
|
|
|
|
|
|
|
vocabularies => $state->{vocabularies}, # reference, not copy |
|
255
|
382
|
|
|
|
|
91237
|
}; |
|
256
|
|
|
|
|
|
|
|
|
257
|
382
|
|
|
|
|
51762
|
my $metaschema_doc; |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# evaluate the document against its metaschema to find any errors, to identify all schema |
|
260
|
|
|
|
|
|
|
# resources within to add to the global resource index, and to extract all operationIds |
|
261
|
382
|
|
|
|
|
840
|
my (@json_schema_paths, @operation_paths, %bad_path_item_refs, @server_paths, %tag_operation_paths, @bad_3_0_paths, @references); |
|
262
|
784
|
|
|
|
|
1803
|
my $result = $evaluator->evaluate( |
|
263
|
|
|
|
|
|
|
$schema, $self->metaschema_uri, |
|
264
|
|
|
|
|
|
|
{ |
|
265
|
|
|
|
|
|
|
collect_annotations => 0, |
|
266
|
|
|
|
|
|
|
validate_formats => 1, |
|
267
|
|
|
|
|
|
|
callbacks => { |
|
268
|
|
|
|
|
|
|
# we avoid producing errors here so we don't create extra errors for "not all additional |
|
269
|
|
|
|
|
|
|
# properties are valid" etc |
|
270
|
784
|
|
|
784
|
|
3818020
|
'$dynamicRef' => sub ($, $schema, $state) { |
|
|
784
|
|
|
|
|
1448
|
|
|
|
784
|
|
|
|
|
1313
|
|
|
271
|
|
|
|
|
|
|
# Note that if we are using the default metaschema |
|
272
|
|
|
|
|
|
|
# https://spec.openapis.org/oas//schema/, we will only find the root of each |
|
273
|
|
|
|
|
|
|
# schema, not all subschemas. We will traverse each of these schemas later using |
|
274
|
|
|
|
|
|
|
# jsonSchemaDialect to find all subschemas and their $ids. |
|
275
|
784
|
50
|
|
|
|
4829
|
push @json_schema_paths, $state->{data_path} if $schema->{'$dynamicRef'} eq '#meta'; |
|
276
|
784
|
|
|
|
|
1784
|
return 1; |
|
277
|
|
|
|
|
|
|
}, |
|
278
|
11680
|
|
|
11680
|
|
51943266
|
'$ref' => sub ($data, $schema, $state) { |
|
|
11680
|
|
|
|
|
20972
|
|
|
|
11680
|
|
|
|
|
15151
|
|
|
|
11680
|
|
|
|
|
13765
|
|
|
|
11680
|
|
|
|
|
12497
|
|
|
279
|
11680
|
|
|
|
|
14108
|
my $entity; |
|
280
|
|
|
|
|
|
|
|
|
281
|
11680
|
100
|
|
|
|
47636
|
if ($self->oas_version eq '3.0') { |
|
282
|
|
|
|
|
|
|
# strip '#/definitions/'; convert CamelCase to kebab-case |
|
283
|
155
|
50
|
|
|
|
1606
|
if ($entity = lc join('-', split /(?=[A-Z])/, substr($schema->{'$ref'}, 14))) { |
|
284
|
155
|
100
|
|
|
|
462
|
if ($entity eq 'schema') { |
|
285
|
|
|
|
|
|
|
push @bad_3_0_paths, [ items => $state->{data_path} ] |
|
286
|
33
|
100
|
100
|
|
|
232
|
if ($data->{type}//'') eq 'array' and not exists $data->{items}; |
|
|
|
|
100
|
|
|
|
|
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
push @bad_3_0_paths, [ minimum => $state->{data_path} ] |
|
289
|
33
|
100
|
100
|
|
|
190
|
if exists $data->{exclusiveMinimum} and not exists $data->{minimum}; |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
push @bad_3_0_paths, [ maximum => $state->{data_path} ] |
|
292
|
33
|
100
|
100
|
|
|
146
|
if exists $data->{exclusiveMaximum} and not exists $data->{maximum}; |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# "$ref" in path-item is not represented in the schema by a Reference object |
|
296
|
|
|
|
|
|
|
push @references, [ '$ref', $state->{data_path}, Mojo::URL->new($data->{'$ref'})->to_abs($self->canonical_uri), 'path-item' ] |
|
297
|
155
|
100
|
100
|
|
|
483
|
if $entity eq 'path-item' and exists $data->{'$ref'}; |
|
298
|
|
|
|
|
|
|
|
|
299
|
155
|
100
|
|
|
|
1527
|
if ($entity eq 'reference') { |
|
300
|
16
|
|
66
|
|
|
70
|
$metaschema_doc //= $evaluator->_get_resource($self->metaschema_uri)->{document}; |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# in the 3.0 metaschema, entities are identified via: |
|
303
|
|
|
|
|
|
|
# "oneOf": [ { "$ref": "#/definitions/Foo" }, { "$ref": "#/definitions/Reference" } ] |
|
304
|
16
|
|
50
|
|
|
1286
|
my $schema_path = ($state->{initial_schema_uri}->fragment//'').$state->{keyword_path}; |
|
305
|
16
|
50
|
|
|
|
193
|
if ($schema_path =~ s{/oneOf/\K([01])\z}{$1 ^ 1}e) { |
|
|
16
|
|
|
|
|
94
|
|
|
306
|
16
|
|
|
|
|
66
|
$entity = lc join('-', split /(?=[A-Z])/, substr($metaschema_doc->get($schema_path)->{'$ref'}, 14)); |
|
307
|
16
|
100
|
|
|
|
1056
|
$entity .= 's' if $entity eq 'callback'; |
|
308
|
16
|
|
|
|
|
94
|
push @references, [ '$ref', $state->{data_path}, Mojo::URL->new($data->{'$ref'})->to_abs($self->canonical_uri), $entity ]; |
|
309
|
|
|
|
|
|
|
} |
|
310
|
|
|
|
|
|
|
} |
|
311
|
|
|
|
|
|
|
|
|
312
|
155
|
100
|
|
|
|
6540
|
$entity .= 's' if $entity eq 'callback'; |
|
313
|
155
|
100
|
|
|
|
458
|
undef $entity if not grep $entity eq $_, __entities; |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# no need to push to @json_schema_paths, as all schema entities are already found |
|
316
|
|
|
|
|
|
|
# via $refs above, and there are no embedded identifiers to be identified |
|
317
|
|
|
|
|
|
|
} |
|
318
|
|
|
|
|
|
|
} |
|
319
|
|
|
|
|
|
|
else { |
|
320
|
|
|
|
|
|
|
# we only need to special-case path-item, because this is the only entity that is |
|
321
|
|
|
|
|
|
|
# referenced in the schema without an -or-reference |
|
322
|
|
|
|
|
|
|
($entity) = (($schema->{'$ref'} =~ m{#/\$defs/([^/]+?)(?:-or-reference)\z}), |
|
323
|
11525
|
|
|
|
|
53792
|
($schema->{'$ref'} =~ m{#/\$defs/(path-item)\z})); |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
push @references, [ '$ref', $state->{data_path}, Mojo::URL->new($data->{'$ref'})->to_abs($self->canonical_uri), 'path-item' ] |
|
326
|
11525
|
100
|
100
|
|
|
50788
|
if ($entity//'') eq 'path-item' and exists $data->{'$ref'}; |
|
|
|
|
100
|
|
|
|
|
|
327
|
|
|
|
|
|
|
|
|
328
|
11525
|
100
|
|
|
|
39015
|
if ($schema->{'$ref'} eq '#/$defs/reference') { |
|
329
|
109
|
|
|
|
|
508
|
my ($e) = ($state->{initial_schema_uri}->fragment =~ m{/\$defs/([^/]+?)(?:-or-reference)\z}); |
|
330
|
109
|
|
|
|
|
1809
|
push @references, [ '$ref', $state->{data_path}, Mojo::URL->new($data->{'$ref'})->to_abs($self->canonical_uri), $e ]; |
|
331
|
|
|
|
|
|
|
} |
|
332
|
|
|
|
|
|
|
} |
|
333
|
|
|
|
|
|
|
|
|
334
|
11680
|
100
|
|
|
|
67239
|
$self->_add_entity_location($state->{data_path}, $entity) if $entity; |
|
335
|
|
|
|
|
|
|
|
|
336
|
11680
|
100
|
100
|
|
|
135702
|
if ($schema->{'$ref'} eq '#/$defs/operation' or $schema->{'$ref'} eq '#/definitions/Operation') { |
|
337
|
|
|
|
|
|
|
push @operation_paths, [ $data->{operationId} => $state->{data_path} ] |
|
338
|
439
|
100
|
|
|
|
1919
|
if defined $data->{operationId}; |
|
339
|
|
|
|
|
|
|
|
|
340
|
17
|
|
|
17
|
|
53652
|
{ use autovivification 'store'; |
|
|
17
|
|
|
|
|
23
|
|
|
|
17
|
|
|
|
|
108
|
|
|
|
439
|
|
|
|
|
791
|
|
|
341
|
|
|
|
|
|
|
push $tag_operation_paths{$_}->@*, $state->{data_path} |
|
342
|
439
|
|
100
|
|
|
2516
|
foreach ($data->{tags}//[])->@*; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# path-items are weird and allow mixing of fields adjacent to a $ref, which is burdensome |
|
347
|
|
|
|
|
|
|
# to properly support (see https://github.com/OAI/OpenAPI-Specification/issues/3734) |
|
348
|
11680
|
100
|
100
|
|
|
29856
|
if ($entity and $entity eq 'path-item' and exists $data->{'$ref'}) { |
|
|
|
|
100
|
|
|
|
|
|
349
|
31
|
|
|
|
|
141
|
my %path_item = $data->%*; |
|
350
|
31
|
|
|
|
|
108
|
delete @path_item{qw(summary description $ref)}; |
|
351
|
31
|
100
|
|
|
|
144
|
$bad_path_item_refs{$state->{data_path}} = join(', ', sort keys %path_item) if keys %path_item; |
|
352
|
|
|
|
|
|
|
} |
|
353
|
|
|
|
|
|
|
|
|
354
|
11680
|
100
|
|
|
|
24880
|
push @server_paths, $state->{data_path} if $schema->{'$ref'} eq '#/$defs/server'; |
|
355
|
|
|
|
|
|
|
|
|
356
|
11680
|
|
|
|
|
23556
|
return 1; |
|
357
|
|
|
|
|
|
|
}, |
|
358
|
|
|
|
|
|
|
}, |
|
359
|
|
|
|
|
|
|
}, |
|
360
|
382
|
|
|
|
|
9849
|
); |
|
361
|
|
|
|
|
|
|
|
|
362
|
382
|
100
|
|
|
|
504322
|
if (not $result->valid) { |
|
363
|
13
|
|
|
|
|
343
|
foreach my $e ($result->errors) { |
|
364
|
58
|
50
|
50
|
|
|
473
|
if (($e->keyword//'') eq 'not' |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
365
|
|
|
|
|
|
|
and $e->absolute_keyword_location->fragment eq '/$defs/parameters/not' |
|
366
|
|
|
|
|
|
|
and $e->absolute_keyword_location->clone->fragment(undef) eq DEFAULT_METASCHEMA->{$self->oas_version} |
|
367
|
|
|
|
|
|
|
) { |
|
368
|
1
|
|
|
|
|
469
|
push $state->{errors}->@*, $e->clone( |
|
369
|
|
|
|
|
|
|
keyword_location => '', |
|
370
|
|
|
|
|
|
|
absolute_keyword_location => undef, |
|
371
|
|
|
|
|
|
|
error => 'cannot use query and querystring together', |
|
372
|
|
|
|
|
|
|
); |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
|
|
375
|
58
|
|
|
|
|
275
|
push $state->{errors}->@*, $e; |
|
376
|
|
|
|
|
|
|
} |
|
377
|
|
|
|
|
|
|
|
|
378
|
13
|
|
|
|
|
167
|
return $state; |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
369
|
100
|
|
|
|
9954
|
$self->_set_defaults($result->defaults) if $result->defaults; |
|
382
|
|
|
|
|
|
|
|
|
383
|
369
|
|
|
|
|
4181
|
foreach my $pair (@operation_paths) { |
|
384
|
88
|
|
|
|
|
49939
|
my ($operation_id, $path) = @$pair; |
|
385
|
88
|
100
|
|
|
|
361
|
if (my $existing = $self->operationId_path($operation_id)) { |
|
386
|
6
|
|
|
|
|
84
|
()= E({ %$state, keyword_path => $path .'/operationId' }, |
|
387
|
|
|
|
|
|
|
'duplicate of operationId at %s', $existing); |
|
388
|
|
|
|
|
|
|
} |
|
389
|
|
|
|
|
|
|
else { |
|
390
|
82
|
|
|
|
|
1161
|
$self->_add_operationId($operation_id => $path); |
|
391
|
|
|
|
|
|
|
} |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $_->[1] }, |
|
395
|
|
|
|
|
|
|
$_->[0] eq 'items' ? '"items" must be present if type is "array"' |
|
396
|
|
|
|
|
|
|
: $_->[0] eq 'minimum' ? '"minimum" must be present when "exclusiveMinimum" is used' |
|
397
|
|
|
|
|
|
|
: $_->[0] eq 'maximum' ? '"maximum" must be present when "exclusiveMaximum" is used' |
|
398
|
|
|
|
|
|
|
: die |
|
399
|
369
|
50
|
|
|
|
47220
|
) foreach @bad_3_0_paths; |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# v3.2.0 ยง4.8.1, "Patterned Fields": "When matching URLs, concrete (non-templated) paths would be |
|
402
|
|
|
|
|
|
|
# matched before their templated counterparts." |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# caution, Schwartzian transform ahead! |
|
405
|
|
|
|
|
|
|
$self->_set_path_templates(my $sorted_paths = [ |
|
406
|
|
|
|
|
|
|
map $_->[0], # remove transformed entries |
|
407
|
209
|
50
|
|
|
|
2377
|
sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] } # sort by the transformed entries |
|
408
|
|
|
|
|
|
|
map [ $_, s/\{[^{}]+\}/\x{10FFFF}/rg ], # transform template names into the highest Unicode char |
|
409
|
|
|
|
|
|
|
grep !/^x-/, # remove extension keywords |
|
410
|
369
|
|
100
|
|
|
11150
|
keys(($schema->{paths}//{})->%*) # all entries in /paths/* |
|
411
|
|
|
|
|
|
|
]); |
|
412
|
|
|
|
|
|
|
|
|
413
|
369
|
|
|
|
|
5591
|
my %seen_path; |
|
414
|
369
|
|
|
|
|
1153
|
foreach my $path (@$sorted_paths) { |
|
415
|
|
|
|
|
|
|
# see ABNF at v3.2.0 ยง4.8.2 |
|
416
|
407
|
50
|
|
|
|
1702
|
die "invalid path: $path" if substr($path, 0, 1) ne '/'; # schema validation catches this |
|
417
|
407
|
100
|
|
|
|
3383
|
()= E({ %$state, keyword_path => jsonp('/paths', $path) }, 'invalid path template "%s"', $path) |
|
418
|
|
|
|
|
|
|
if grep !/^(?:\{[^{}]+\}|%[0-9A-Fa-f]{2}|[:@!\$&'()*+,;=A-Za-z0-9._~-]+)+\z/, |
|
419
|
|
|
|
|
|
|
split('/', substr($path, 1)); # split by segment, omitting leading / |
|
420
|
|
|
|
|
|
|
|
|
421
|
407
|
|
|
|
|
1137
|
my %seen_names; |
|
422
|
407
|
|
|
|
|
1373
|
foreach my $name ($path =~ /\{([^{}]+)\}/g) { |
|
423
|
|
|
|
|
|
|
# v3.2.0 ยง4.8.1, "Patterned Fields": "Templated paths with the same hierarchy but different |
|
424
|
|
|
|
|
|
|
# templated names MUST NOT exist as they are identical." |
|
425
|
178
|
100
|
|
|
|
854
|
if (++$seen_names{$name} == 2) { |
|
426
|
2
|
|
|
|
|
9
|
()= E({ %$state, keyword_path => jsonp('/paths', $path) }, |
|
427
|
|
|
|
|
|
|
'duplicate path template variable "%s"', $name); |
|
428
|
|
|
|
|
|
|
} |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
|
|
431
|
407
|
|
|
|
|
1429
|
my $normalized = $path =~ s/\{[^{}]+\}/\x00/gr; |
|
432
|
407
|
100
|
|
|
|
1276
|
if (my $first_path = $seen_path{$normalized}) { |
|
433
|
3
|
|
|
|
|
29
|
()= E({ %$state, keyword_path => jsonp('/paths', $path) }, |
|
434
|
|
|
|
|
|
|
'duplicate of templated path "%s"', $first_path); |
|
435
|
3
|
|
|
|
|
955
|
next; |
|
436
|
|
|
|
|
|
|
} |
|
437
|
404
|
|
|
|
|
1189
|
$seen_path{$normalized} = $path; |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
|
|
440
|
369
|
|
|
|
|
955
|
foreach my $path_item (sort keys %bad_path_item_refs) { |
|
441
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $path_item }, |
|
442
|
4
|
|
|
|
|
528
|
'invalid keywords used adjacent to $ref in a path-item: %s', $bad_path_item_refs{$path_item}); |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
|
|
445
|
369
|
|
|
|
|
866
|
my %seen_url; # indexed by servers object |
|
446
|
369
|
|
|
|
|
851
|
foreach my $server_location (@server_paths) { |
|
447
|
78
|
|
|
|
|
2427
|
my $server = $self->get($server_location); |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# see ABNF at v3.2.0 ยง4.6 |
|
450
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $server_location.'/url' }, |
|
451
|
|
|
|
|
|
|
'invalid server url "%s"', $server->{url}), next |
|
452
|
78
|
100
|
|
|
|
3480
|
if $server->{url} !~ /^(?:\{[^{}]+\}|%[0-9A-Fa-f]{2}|[\x21\x24\x26-\x3B\x3D\x40-\x5B\x5D\x5F\x61-\x7A\x7E\xA0-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}\x{E000}-\x{F8FF}\x{F0000}-\x{FFFFD}\x{100000}-\x{10FFFD}])+\z/; |
|
453
|
|
|
|
|
|
|
|
|
454
|
66
|
|
|
|
|
224
|
my $normalized = $server->{url} =~ s/\{[^{}]+\}/\x00/gr; |
|
455
|
66
|
|
|
|
|
201
|
my @url_variables = $server->{url} =~ /\{([^{}]+)\}/g; |
|
456
|
|
|
|
|
|
|
|
|
457
|
66
|
|
|
|
|
205
|
my $servers_location = $server_location =~ s{/[0-9]+\z}{}r; |
|
458
|
|
|
|
|
|
|
|
|
459
|
66
|
100
|
100
|
|
|
357
|
if (my $first_url = ($seen_url{$servers_location}//{})->{$normalized}) { |
|
460
|
6
|
|
|
|
|
61
|
()= E({ %$state, keyword_path => $server_location.'/url' }, |
|
461
|
|
|
|
|
|
|
'duplicate of templated server url "%s"', $first_url); |
|
462
|
|
|
|
|
|
|
} |
|
463
|
17
|
|
|
17
|
|
26148
|
{ use autovivification 'store'; $seen_url{$servers_location}->{$normalized} = $server->{url}; } |
|
|
17
|
|
|
|
|
27
|
|
|
|
17
|
|
|
|
|
76
|
|
|
|
66
|
|
|
|
|
984
|
|
|
|
66
|
|
|
|
|
175
|
|
|
464
|
|
|
|
|
|
|
|
|
465
|
66
|
|
|
|
|
106
|
my $variables_obj = $server->{variables}; |
|
466
|
66
|
100
|
|
|
|
109
|
if (not $variables_obj) { |
|
467
|
|
|
|
|
|
|
# missing 'variables': needs variables/$varname/default |
|
468
|
33
|
100
|
|
|
|
70
|
()= E({ %$state, keyword_path => $server_location }, |
|
469
|
|
|
|
|
|
|
'"variables" property is required for templated server urls') if @url_variables; |
|
470
|
33
|
|
|
|
|
490
|
next; |
|
471
|
|
|
|
|
|
|
} |
|
472
|
|
|
|
|
|
|
|
|
473
|
33
|
|
|
|
|
35
|
my %seen_names; |
|
474
|
33
|
|
|
|
|
47
|
foreach my $name (@url_variables) { |
|
475
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $server_location }, |
|
476
|
|
|
|
|
|
|
'duplicate server template variable "%s"', $name) |
|
477
|
49
|
100
|
|
|
|
551
|
if ++$seen_names{$name} == 2; |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $server_location.'/variables' }, |
|
480
|
|
|
|
|
|
|
'missing "variables" definition for server template variable "%s"', $name) |
|
481
|
49
|
100
|
100
|
|
|
624
|
if $seen_names{$name} == 1 and not exists $variables_obj->{$name}; |
|
482
|
|
|
|
|
|
|
} |
|
483
|
|
|
|
|
|
|
|
|
484
|
33
|
|
|
|
|
82
|
foreach my $varname (keys $variables_obj->%*) { |
|
485
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => jsonp($server_location, 'variables', $varname, 'default') }, |
|
486
|
|
|
|
|
|
|
'server default is not a member of enum') |
|
487
|
|
|
|
|
|
|
if exists $variables_obj->{$varname}{enum} |
|
488
|
49
|
100
|
100
|
|
|
892
|
and not grep $variables_obj->{$varname}{default} eq $_, $variables_obj->{$varname}{enum}->@*; |
|
489
|
|
|
|
|
|
|
} |
|
490
|
|
|
|
|
|
|
} |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
# name -> index; for duplicates, will contain the first index where the tag can be found |
|
493
|
369
|
|
100
|
|
|
3408
|
my %tag_to_index = reverse indexed map $_->{name}, ($schema->{tags}//[])->@*; |
|
494
|
|
|
|
|
|
|
|
|
495
|
369
|
|
100
|
|
|
2710
|
foreach my $tag_idx (0 .. ($schema->{tags}//[])->$#*) { |
|
496
|
12
|
|
|
|
|
303
|
my $tag = $schema->{tags}[$tag_idx]; |
|
497
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => '/tags/'.$tag_idx.'/name' }, |
|
498
|
|
|
|
|
|
|
'duplicate of tag at /tags/%d: "%s"', $tag_to_index{$tag->{name}}, $tag->{name}) |
|
499
|
12
|
100
|
|
|
|
82
|
if $tag_to_index{$tag->{name}} != $tag_idx; |
|
500
|
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => '/tags/'.$tag_idx.'/parent' }, |
|
502
|
|
|
|
|
|
|
'parent of tag "%s" does not exist: "%s"', $tag->{name}, $tag->{parent}) |
|
503
|
12
|
100
|
100
|
|
|
537
|
if exists $tag->{parent} and not exists $tag_to_index{$tag->{parent}}; |
|
504
|
|
|
|
|
|
|
|
|
505
|
12
|
|
|
|
|
239
|
my @seen; |
|
506
|
12
|
|
|
|
|
33
|
while (defined $tag->{parent}) { |
|
507
|
12
|
|
|
|
|
23
|
push @seen, $tag->{name}; |
|
508
|
12
|
100
|
|
|
|
30
|
last if not defined $tag_to_index{$tag->{parent}}; |
|
509
|
11
|
|
|
|
|
23
|
$tag = $schema->{tags}[$tag_to_index{$tag->{parent}}]; |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => '/tags/'.$tag_idx.'/parent' }, |
|
512
|
|
|
|
|
|
|
'circular reference between tags: '.join(' -> ', map '"'.$_.'"', @seen, $tag->{name})), |
|
513
|
|
|
|
|
|
|
last |
|
514
|
11
|
100
|
|
|
|
125
|
if grep $_ eq $tag->{name}, @seen; |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
} |
|
517
|
|
|
|
|
|
|
|
|
518
|
369
|
100
|
|
|
|
2300
|
return $state if $state->{errors}->@*; |
|
519
|
|
|
|
|
|
|
|
|
520
|
363
|
|
|
|
|
2134
|
$self->{_tags} = (HashRef[json_pointer_type])->({ map +($_ => '/tags/'.$tag_to_index{$_}), keys %tag_to_index }); |
|
521
|
363
|
|
|
|
|
777865
|
$self->{_operation_tags} = (HashRef[ArrayRef[json_pointer_type]])->(\%tag_operation_paths); |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# disregard paths that are not the root of each embedded subschema. |
|
524
|
|
|
|
|
|
|
# Because the callbacks are executed after the keyword has (recursively) finished evaluating, |
|
525
|
|
|
|
|
|
|
# for each nested schema group. the schema paths appear longest first, with the parent schema |
|
526
|
|
|
|
|
|
|
# appearing last. Therefore we can whittle down to the parent schema for each group by iterating |
|
527
|
|
|
|
|
|
|
# through the full list in reverse, and checking if it is a child of the last path we chose to save. |
|
528
|
|
|
|
|
|
|
# When the default metaschema is being used, there is no pruning to be done, as only the root of |
|
529
|
|
|
|
|
|
|
# each embedded schema will be found via callbacks. |
|
530
|
363
|
|
|
|
|
1080917
|
my @real_json_schema_paths; |
|
531
|
363
|
|
|
|
|
2051
|
for (my $idx = $#json_schema_paths; $idx >= 0; --$idx) { |
|
532
|
780
|
50
|
66
|
|
|
3826
|
next if $idx != $#json_schema_paths |
|
533
|
|
|
|
|
|
|
and substr($json_schema_paths[$idx], 0, length($real_json_schema_paths[-1])+1) |
|
534
|
|
|
|
|
|
|
eq $real_json_schema_paths[-1].'/'; |
|
535
|
|
|
|
|
|
|
|
|
536
|
780
|
|
|
|
|
1717
|
push @real_json_schema_paths, $json_schema_paths[$idx]; |
|
537
|
|
|
|
|
|
|
} |
|
538
|
|
|
|
|
|
|
|
|
539
|
363
|
50
|
|
|
|
2539
|
push $state->{references}->@*, @references if $state->{references}; |
|
540
|
|
|
|
|
|
|
|
|
541
|
363
|
|
|
|
|
5063
|
$self->_traverse_schema({ %$state, keyword_path => $_ }) foreach reverse @real_json_schema_paths; |
|
542
|
363
|
100
|
|
|
|
1883
|
return $state if $state->{errors}->@*; |
|
543
|
|
|
|
|
|
|
|
|
544
|
359
|
|
|
|
|
2220
|
$self->_add_entity_location($_, 'schema') foreach $state->{subschemas}->@*; |
|
545
|
|
|
|
|
|
|
|
|
546
|
359
|
|
|
|
|
17423
|
return $state; |
|
547
|
|
|
|
|
|
|
} |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
# just like the base class's version, except we skip the evaluate step because we already did |
|
550
|
|
|
|
|
|
|
# that as part of traverse. |
|
551
|
6
|
|
|
6
|
1
|
583018
|
sub validate ($class, %args) { |
|
|
6
|
|
|
|
|
15
|
|
|
|
6
|
|
|
|
|
23
|
|
|
|
6
|
|
|
|
|
10
|
|
|
552
|
6
|
|
|
|
|
21
|
my $with_defaults = delete $args{with_defaults}; |
|
553
|
|
|
|
|
|
|
|
|
554
|
6
|
50
|
|
|
|
175
|
my $document = blessed($class) ? $class : $class->new(%args); |
|
555
|
6
|
50
|
|
|
|
2803
|
return JSON::Schema::Modern::Result->new( |
|
556
|
|
|
|
|
|
|
errors => [ $document->errors ], |
|
557
|
|
|
|
|
|
|
$with_defaults ? (defaults => $document->defaults) : (), |
|
558
|
|
|
|
|
|
|
); |
|
559
|
|
|
|
|
|
|
} |
|
560
|
|
|
|
|
|
|
|
|
561
|
7
|
|
|
7
|
1
|
4672
|
sub upgrade ($self, $to_version = SUPPORTED_OAD_VERSIONS->[-1]) { |
|
|
7
|
|
|
|
|
18
|
|
|
|
7
|
|
|
|
|
14
|
|
|
|
7
|
|
|
|
|
14
|
|
|
562
|
7
|
50
|
|
|
|
32
|
croak 'cannot upgrade an invalid document' if $self->errors; |
|
563
|
|
|
|
|
|
|
|
|
564
|
7
|
100
|
|
|
|
368
|
croak 'new openapi version must be a dotted tuple or triple' |
|
565
|
|
|
|
|
|
|
if $to_version !~ /^(3\.\d+)(?:\.\d+)?\z/a; |
|
566
|
6
|
|
|
|
|
24
|
my $to_oas_version = $1; |
|
567
|
6
|
100
|
|
|
|
186
|
croak 'requested upgrade to an unsupported version: ', $to_version |
|
568
|
|
|
|
|
|
|
if not grep $to_oas_version eq $_, OAS_VERSIONS->@*; |
|
569
|
|
|
|
|
|
|
|
|
570
|
5
|
100
|
|
|
|
104
|
($to_version) = grep /^$to_version\./, SUPPORTED_OAD_VERSIONS->@* if $to_version =~ /^(3\.\d+)\z/a; |
|
571
|
|
|
|
|
|
|
|
|
572
|
5
|
|
|
|
|
21
|
my $schema = $self->schema; |
|
573
|
|
|
|
|
|
|
|
|
574
|
5
|
|
|
|
|
19
|
my $from_version = $schema->{openapi}; |
|
575
|
5
|
100
|
|
|
|
23
|
return $schema if $from_version eq $to_version; |
|
576
|
|
|
|
|
|
|
|
|
577
|
4
|
|
|
|
|
25
|
my ($from_oas_version) = $schema->{openapi} =~ /^(3\.\d+)\.\d+\b/a; |
|
578
|
4
|
100
|
|
|
|
278
|
croak 'downgrading is not supported' if $from_oas_version > $to_oas_version; |
|
579
|
|
|
|
|
|
|
|
|
580
|
3
|
|
|
|
|
10
|
$schema->{openapi} = $to_version; |
|
581
|
|
|
|
|
|
|
|
|
582
|
3
|
100
|
|
|
|
13
|
return $schema if $from_oas_version eq $to_oas_version; |
|
583
|
|
|
|
|
|
|
|
|
584
|
2
|
100
|
|
|
|
7
|
if ($from_oas_version eq '3.0') { |
|
585
|
1
|
50
|
33
|
|
|
13
|
delete $schema->{paths} if not keys $schema->{paths}->%* and exists $schema->{components}; |
|
586
|
|
|
|
|
|
|
|
|
587
|
1
|
|
|
|
|
11
|
foreach my $media_type_path ($self->get_entity_locations('media-type')) { |
|
588
|
1
|
|
|
|
|
40
|
my $media_type = (unjsonp($media_type_path))[-1]; |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
# convert {"schema": {"type": "string", "format": "binary"}} to {} |
|
591
|
1
|
50
|
|
|
|
25
|
if ($media_type eq 'application/octet-stream') { |
|
592
|
1
|
|
|
|
|
7
|
my $media_type_obj = $self->get($media_type_path); |
|
593
|
1
|
|
|
|
|
51
|
my $schema = $media_type_obj->{schema}; |
|
594
|
1
|
50
|
33
|
|
|
32
|
if ($schema and keys %$schema == 2 |
|
|
|
|
50
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
595
|
|
|
|
|
|
|
and ($schema->{type}//'') eq 'string' and ($schema->{format}//'') eq 'binary') { |
|
596
|
1
|
|
|
|
|
4
|
delete $media_type_obj->{schema}; |
|
597
|
1
|
|
|
|
|
26
|
delete $self->_entities->{$media_type_path.'/schema'}; |
|
598
|
|
|
|
|
|
|
} |
|
599
|
|
|
|
|
|
|
} |
|
600
|
|
|
|
|
|
|
} |
|
601
|
|
|
|
|
|
|
|
|
602
|
1
|
|
|
|
|
17
|
foreach my $schema_path ($self->get_entity_locations('schema')) { |
|
603
|
6
|
|
|
|
|
32
|
my $subschema = $self->get($schema_path); |
|
604
|
|
|
|
|
|
|
|
|
605
|
6
|
100
|
|
|
|
196
|
if (exists $subschema->{nullable}) { |
|
606
|
|
|
|
|
|
|
$subschema->{type} = [ $subschema->{type}, 'null' ] |
|
607
|
2
|
100
|
66
|
|
|
10
|
if delete $subschema->{nullable} and exists $subschema->{type}; |
|
608
|
|
|
|
|
|
|
} |
|
609
|
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
$subschema->{const} = (delete $subschema->{enum})->[0] |
|
611
|
6
|
100
|
100
|
|
|
41
|
if exists $subschema->{enum} and $subschema->{enum}->@* == 1; |
|
612
|
|
|
|
|
|
|
|
|
613
|
6
|
100
|
|
|
|
11
|
$subschema->{exclusiveMinimum} = delete $subschema->{minimum} if delete $subschema->{exclusiveMinimum}; |
|
614
|
6
|
100
|
|
|
|
17
|
$subschema->{exclusiveMaximum} = delete $subschema->{maximum} if delete $subschema->{exclusiveMaximum}; |
|
615
|
|
|
|
|
|
|
|
|
616
|
6
|
100
|
|
|
|
17
|
$subschema->{examples} = [ delete $subschema->{example} ] if exists $subschema->{example}; |
|
617
|
|
|
|
|
|
|
|
|
618
|
6
|
100
|
|
|
|
13
|
if (exists $subschema->{format}) { |
|
619
|
2
|
100
|
|
|
|
10
|
if ($subschema->{format} eq 'binary') { |
|
|
|
50
|
|
|
|
|
|
|
620
|
1
|
|
|
|
|
4
|
$subschema->{contentMediaType} = 'application/octet-stream'; |
|
621
|
1
|
|
|
|
|
3
|
delete $subschema->{format}; |
|
622
|
|
|
|
|
|
|
} |
|
623
|
|
|
|
|
|
|
elsif ($subschema->{format} eq 'base64') { |
|
624
|
1
|
|
|
|
|
3
|
$subschema->{contentEncoding} = 'base64'; |
|
625
|
1
|
|
|
|
|
2
|
delete $subschema->{format}; |
|
626
|
|
|
|
|
|
|
} |
|
627
|
|
|
|
|
|
|
} |
|
628
|
|
|
|
|
|
|
} |
|
629
|
|
|
|
|
|
|
} |
|
630
|
|
|
|
|
|
|
|
|
631
|
2
|
100
|
|
|
|
9
|
if ($to_oas_version ge '3.2') { |
|
632
|
1
|
|
|
|
|
5
|
foreach my $schema_path ($self->get_entity_locations('response')) { |
|
633
|
2
|
|
|
|
|
38
|
my $subschema = $self->get($schema_path); |
|
634
|
|
|
|
|
|
|
delete $subschema->{description} |
|
635
|
2
|
100
|
66
|
|
|
80
|
if exists $subschema->{description} and $subschema->{description} eq ''; |
|
636
|
|
|
|
|
|
|
} |
|
637
|
|
|
|
|
|
|
} |
|
638
|
|
|
|
|
|
|
|
|
639
|
2
|
|
|
|
|
15
|
return $schema; |
|
640
|
|
|
|
|
|
|
} |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
######## NO PUBLIC INTERFACES FOLLOW THIS POINT ######## |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
# https://spec.openapis.org/oas/latest#schema-object |
|
645
|
|
|
|
|
|
|
# traverse this JSON Schema and identify all errors, subschema locations, and referenceable |
|
646
|
|
|
|
|
|
|
# identifiers |
|
647
|
780
|
|
|
780
|
|
1500
|
sub _traverse_schema ($self, $state) { |
|
|
780
|
|
|
|
|
1246
|
|
|
|
780
|
|
|
|
|
1232
|
|
|
|
780
|
|
|
|
|
969
|
|
|
648
|
780
|
|
|
|
|
3615
|
my $schema = $self->get($state->{keyword_path}); |
|
649
|
|
|
|
|
|
|
|
|
650
|
780
|
100
|
100
|
|
|
44014
|
if (get_type($schema) eq 'boolean' or not keys %$schema) { |
|
651
|
163
|
|
|
|
|
2617
|
push $state->{subschemas}->@*, $state->{keyword_path}; |
|
652
|
163
|
|
|
|
|
1134
|
return; |
|
653
|
|
|
|
|
|
|
} |
|
654
|
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
my $subschema_state = $state->{evaluator}->traverse($schema, { |
|
656
|
|
|
|
|
|
|
initial_schema_uri => canonical_uri($state), |
|
657
|
|
|
|
|
|
|
traversed_keyword_path => $state->{traversed_keyword_path}.$state->{keyword_path}, |
|
658
|
|
|
|
|
|
|
metaschema_uri => $state->{json_schema_dialect}, # can be overridden with the '$schema' keyword |
|
659
|
617
|
|
|
|
|
8676
|
}); |
|
660
|
|
|
|
|
|
|
|
|
661
|
617
|
|
|
|
|
817471
|
push $state->{errors}->@*, $subschema_state->{errors}->@*; |
|
662
|
617
|
|
|
|
|
2647
|
push $state->{subschemas}->@*, $subschema_state->{subschemas}->@*; |
|
663
|
617
|
50
|
50
|
|
|
3485
|
push $state->{references}->@*, ($subschema_state->{references}//[])->@* if $state->{references}; |
|
664
|
|
|
|
|
|
|
|
|
665
|
617
|
|
|
|
|
10087
|
foreach my $new_uri (sort keys $subschema_state->{identifiers}->%*) { |
|
666
|
29
|
100
|
|
|
|
100
|
if (not $state->{identifiers}{$new_uri}) { |
|
667
|
22
|
|
|
|
|
78
|
$state->{identifiers}{$new_uri} = $subschema_state->{identifiers}{$new_uri}; |
|
668
|
22
|
|
|
|
|
253
|
next; |
|
669
|
|
|
|
|
|
|
} |
|
670
|
|
|
|
|
|
|
|
|
671
|
7
|
|
|
|
|
15
|
my $existing = $state->{identifiers}{$new_uri}; |
|
672
|
7
|
|
|
|
|
17
|
my $new = $subschema_state->{identifiers}{$new_uri}; |
|
673
|
|
|
|
|
|
|
|
|
674
|
7
|
100
|
|
|
|
26
|
if (not is_equal( |
|
675
|
|
|
|
|
|
|
{ canonical_uri => $new->{canonical_uri}.'', map +($_ => $new->{$_}), qw(path specification_version vocabularies) }, |
|
676
|
|
|
|
|
|
|
{ canonical_uri => $existing->{canonical_uri}.'', map +($_ => $existing->{$_}), qw(path specification_version vocabularies) })) { |
|
677
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $new->{path} }, |
|
678
|
|
|
|
|
|
|
'duplicate canonical uri "%s" found (original at path "%s")', |
|
679
|
2
|
|
|
|
|
1373
|
$new_uri, $existing->{path}); |
|
680
|
2
|
|
|
|
|
395
|
next; |
|
681
|
|
|
|
|
|
|
} |
|
682
|
|
|
|
|
|
|
|
|
683
|
5
|
|
|
|
|
5274
|
foreach my $anchor (sort keys $new->{anchors}->%*) { |
|
684
|
5
|
100
|
100
|
|
|
30
|
if (my $existing_anchor = ($existing->{anchors}//{})->{$anchor}) { |
|
685
|
|
|
|
|
|
|
()= E({ %$state, keyword_path => $new->{anchors}{$anchor}{path} }, |
|
686
|
|
|
|
|
|
|
'duplicate anchor uri "%s" found (original at path "%s")', |
|
687
|
|
|
|
|
|
|
$new->{canonical_uri}->clone->fragment($anchor), |
|
688
|
2
|
|
|
|
|
29
|
$existing->{anchors}{$anchor}{path}); |
|
689
|
2
|
|
|
|
|
915
|
next; |
|
690
|
|
|
|
|
|
|
} |
|
691
|
|
|
|
|
|
|
|
|
692
|
17
|
|
|
17
|
|
51914
|
use autovivification 'store'; |
|
|
17
|
|
|
|
|
31
|
|
|
|
17
|
|
|
|
|
74
|
|
|
693
|
3
|
|
|
|
|
76
|
$existing->{anchors}{$anchor} = $new->{anchors}{$anchor}; |
|
694
|
|
|
|
|
|
|
} |
|
695
|
|
|
|
|
|
|
} |
|
696
|
|
|
|
|
|
|
} |
|
697
|
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# Given a jsonSchemaDialect uri, generate a new schema that wraps the standard OAD schema |
|
699
|
|
|
|
|
|
|
# to set the jsonSchemaDialect value for the #meta dynamic reference. |
|
700
|
|
|
|
|
|
|
# This metaschema does not allow subschemas to select their own $schema; for that, you |
|
701
|
|
|
|
|
|
|
# should construct your own, based on DEFAULT_BASE_METASCHEMA. |
|
702
|
0
|
|
|
0
|
|
0
|
sub _dynamic_metaschema_uri ($self, $json_schema_dialect, $evaluator) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
703
|
0
|
|
|
|
|
0
|
$json_schema_dialect .= ''; |
|
704
|
0
|
|
|
|
|
0
|
my $dialect_uri = 'https://custom-dialect.example.com/' . md5_hex($json_schema_dialect); |
|
705
|
0
|
0
|
|
|
|
0
|
return $dialect_uri if $evaluator->_get_resource($dialect_uri); |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# we use the definition of https://spec.openapis.org/oas//schema-base/ but swap out |
|
708
|
|
|
|
|
|
|
# the dialect reference. |
|
709
|
0
|
|
|
|
|
0
|
my $schema = clone($evaluator->_get_resource(DEFAULT_BASE_METASCHEMA->{$self->oas_version})->{document}->schema); |
|
710
|
0
|
|
|
|
|
0
|
$schema->{'$id'} = $dialect_uri; |
|
711
|
0
|
|
|
|
|
0
|
$schema->{'$defs'}{dialect}{const} = $json_schema_dialect; |
|
712
|
0
|
|
|
|
|
0
|
$schema->{'$defs'}{schema}{'$ref'} = $json_schema_dialect; |
|
713
|
|
|
|
|
|
|
|
|
714
|
0
|
|
|
|
|
0
|
$evaluator->add_document( |
|
715
|
|
|
|
|
|
|
Mojo::URL->new($dialect_uri), |
|
716
|
|
|
|
|
|
|
JSON::Schema::Modern::Document->new( |
|
717
|
|
|
|
|
|
|
schema => $schema, |
|
718
|
|
|
|
|
|
|
evaluator => $evaluator, |
|
719
|
|
|
|
|
|
|
)); |
|
720
|
|
|
|
|
|
|
|
|
721
|
0
|
|
|
|
|
0
|
return $dialect_uri; |
|
722
|
|
|
|
|
|
|
} |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# FREEZE is defined by parent class |
|
725
|
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
# callback hook for Sereal::Decoder |
|
727
|
1
|
|
|
1
|
0
|
1840
|
sub THAW ($class, $serializer, $data) { |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
3
|
|
|
728
|
1
|
|
|
|
|
2
|
delete $data->{evaluator}; |
|
729
|
|
|
|
|
|
|
|
|
730
|
1
|
50
|
|
|
|
6
|
if (defined(my $dialect = delete $data->{json_schema_dialect})) { |
|
731
|
0
|
|
|
|
|
0
|
carp "use of no-longer-supported constructor argument: json_schema_dialect = \"$dialect\"; use \"jsonSchemaDialect\": \"...\" in your OpenAPI document itself"; |
|
732
|
|
|
|
|
|
|
} |
|
733
|
|
|
|
|
|
|
|
|
734
|
1
|
|
|
|
|
2
|
my $self = bless($data, $class); |
|
735
|
1
|
50
|
|
|
|
5
|
$self->{oas_version} = OAS_VERSIONS->[-1] if not exists $self->{oas_version}; |
|
736
|
|
|
|
|
|
|
|
|
737
|
1
|
|
|
|
|
2
|
foreach my $attr (qw(schema _entities)) { |
|
738
|
|
|
|
|
|
|
croak "serialization missing attribute '$attr': perhaps your serialized data was produced for an older version of $class?" |
|
739
|
2
|
50
|
|
|
|
6
|
if not exists $self->{$attr}; |
|
740
|
|
|
|
|
|
|
} |
|
741
|
|
|
|
|
|
|
|
|
742
|
1
|
|
|
|
|
4
|
return $self; |
|
743
|
|
|
|
|
|
|
} |
|
744
|
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
1; |
|
746
|
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
__END__ |