our
$VERSION
=
'0.604'
;
use
5.020;
no
autovivification
warn
=>
qw(fetch store exists delete)
;
use
if
"$]"
>= 5.022,
experimental
=>
're_strict'
;
no
if
"$]"
>= 5.031009,
feature
=>
'indirect'
;
no
if
"$]"
>= 5.033001,
feature
=>
'multidimensional'
;
no
if
"$]"
>= 5.033006,
feature
=>
'bareword_filehandles'
;
sub
vocabulary (
$class
) {
}
sub
evaluation_order (
$class
) { 0 }
sub
keywords (
$class
,
$spec_version
) {
return
(
'$schema'
,
$spec_version
eq
'draft4'
?
'id'
:
'$id'
,
$spec_version
!~ /^draft[467]$/ ?
'$anchor'
: (),
$spec_version
eq
'draft2019-09'
?
'$recursiveAnchor'
: (),
$spec_version
!~ /^draft(?:[467]|2019-09)$/ ?
'$dynamicAnchor'
: (),
'$ref'
,
$spec_version
eq
'draft2019-09'
?
'$recursiveRef'
: (),
$spec_version
!~ /^draft(?:[467]|2019-09)$/ ?
'$dynamicRef'
: (),
$spec_version
!~ /^draft[467]$/ ?
'$vocabulary'
: (),
$spec_version
=~ /^draft[467]$/ ?
'definitions'
:
'$defs'
,
$spec_version
!~ /^draft[46]$/ ?
'$comment'
: (),
);
}
sub
_traverse_keyword_id (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'string'
);
my
$uri
= Mojo::URL->new(
$schema
->{
$state
->{keyword}});
if
(
$state
->{spec_version} =~ /^draft[467]$/) {
if
(
length
(
$uri
->fragment)) {
return
E(
$state
,
'%s cannot change the base uri at the same time as declaring an anchor'
,
$state
->{keyword})
if
length
(
$uri
->clone->fragment(
undef
));
return
$class
->_traverse_keyword_anchor(
$schema
,
$state
);
}
}
else
{
return
if
not assert_uri_reference(
$state
,
$schema
);
return
E(
$state
,
'%s value "%s" cannot have a non-empty fragment'
,
$state
->{keyword},
$schema
->{
$state
->{keyword}})
if
length
$uri
->fragment;
}
$uri
->fragment(
undef
);
return
E(
$state
,
'%s cannot be empty'
,
$state
->{keyword})
if
not
length
$uri
;
$uri
=
$uri
->to_abs(
$state
->{initial_schema_uri})
if
not
$uri
->is_abs;
return
E(
$state
,
'duplicate canonical uri "%s" found (original at path "%s")'
,
$uri
,
$state
->{identifiers}{
$uri
}{path})
if
exists
$state
->{identifiers}{
$uri
};
$state
->{initial_schema_uri} =
$uri
;
$state
->{traversed_schema_path} =
$state
->{traversed_schema_path}.
$state
->{schema_path};
$state
->{schema_path} =
''
;
$state
->{identifiers}{
$uri
} = {
path
=>
$state
->{traversed_schema_path},
canonical_uri
=>
$uri
,
specification_version
=>
$state
->{spec_version},
vocabularies
=>
$state
->{vocabularies},
configs
=>
$state
->{configs},
};
return
1;
}
sub
_eval_keyword_id (
$class
,
$data
,
$schema
,
$state
) {
return
1
if
$state
->{spec_version} =~ /^draft[467]$/ and
$schema
->{
$state
->{keyword}} =~ /^
my
$schema_info
=
$state
->{document}->path_to_resource(
$state
->{document_path}.
$state
->{schema_path});
abort(
$state
,
'failed to resolve "%s" to canonical uri'
,
$state
->{keyword})
if
not
$schema_info
;
$state
->{initial_schema_uri} =
$schema_info
->{canonical_uri}->clone;
$state
->{traversed_schema_path} =
$state
->{traversed_schema_path}.
$state
->{schema_path};
$state
->{document_path} =
$state
->{document_path}.
$state
->{schema_path};
$state
->{schema_path} =
''
;
$state
->{spec_version} =
$schema_info
->{specification_version};
$state
->{vocabularies} =
$schema_info
->{vocabularies};
$state
->@{
keys
$state
->{configs}->%*} =
values
$state
->{configs}->%*;
push
$state
->{dynamic_scope}->@*,
$state
->{initial_schema_uri};
return
1;
}
sub
_traverse_keyword_schema (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'string'
) or not assert_uri(
$state
,
$schema
);
my
(
$spec_version
,
$vocabularies
);
if
(
my
$metaschema_info
=
$state
->{evaluator}->_get_metaschema_vocabulary_classes(
$schema
->{
'$schema'
})) {
(
$spec_version
,
$vocabularies
) =
@$metaschema_info
;
}
else
{
my
$schema_info
=
$state
->{evaluator}->_fetch_from_uri(
$schema
->{
'$schema'
});
return
E(
$state
,
'EXCEPTION: unable to find resource "%s"'
,
$schema
->{
'$schema'
})
if
not
$schema_info
;
return
E(
$state
,
'EXCEPTION: bad reference to $schema "%s": not a schema'
,
$schema_info
->{canonical_uri})
if
$schema_info
->{document}->get_entity_at_location(
$schema_info
->{document_path}) ne
'schema'
;
if
(not is_plain_hashref(
$schema_info
->{schema})) {
()= E(
$state
,
'metaschemas must be objects'
);
}
else
{
(
$spec_version
,
$vocabularies
) =
$state
->{evaluator}->_fetch_vocabulary_data({
%$state
,
keyword
=>
'$vocabulary'
,
initial_schema_uri
=> Mojo::URL->new(
$schema
->{
'$schema'
}),
traversed_schema_path
=> jsonp(
$state
->{schema_path},
'$schema'
) },
$schema_info
);
}
}
return
E(
$state
,
'"%s" is not a valid metaschema'
,
$schema
->{
'$schema'
})
if
not
$vocabularies
or not
@$vocabularies
;
return
E(
$state
,
'$schema can only appear at the schema resource root'
)
if
not
exists
$schema
->{
$spec_version
eq
'draft4'
?
'id'
:
'$id'
}
and
length
(
$state
->{schema_path});
return
E(
$state
,
'$schema and $ref cannot be used together in older drafts'
)
if
exists
$schema
->{
'$ref'
} and
$spec_version
=~ /^draft[467]$/;
$state
->{evaluator}->_set_metaschema_vocabulary_classes(
$schema
->{
'$schema'
}, [
$spec_version
,
$vocabularies
]);
$state
->@{
qw(spec_version vocabularies)
} = (
$spec_version
,
$vocabularies
);
return
1;
}
sub
_eval_keyword_schema (
$class
,
$data
,
$schema
,
$state
) {
$state
->@{
qw(spec_version vocabularies)
} =
$state
->{evaluator}->_get_metaschema_vocabulary_classes(
$schema
->{
'$schema'
})->@*;
return
1;
}
sub
_traverse_keyword_anchor (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'string'
);
my
$anchor
=
$schema
->{
$state
->{keyword}};
return
E(
$state
,
'%s value "%s" does not match required syntax'
,
$state
->{keyword},
$anchor
)
if
$state
->{spec_version} =~ /^draft[467]$/ and
$anchor
!~ /^
or
$state
->{spec_version} eq
'draft2019-09'
and
$anchor
!~ /^[A-Za-z][A-Za-z0-9_:.-]*$/
or
$state
->{spec_version} eq
'draft2020-12'
and
$anchor
!~ /^[A-Za-z_][A-Za-z0-9._-]*$/;
my
$canonical_uri
= canonical_uri(
$state
);
$anchor
=~ s/^
my
$uri
= Mojo::URL->new->to_abs(
$canonical_uri
)->fragment(
$anchor
);
my
$base_uri
=
$canonical_uri
->clone->fragment(
undef
);
if
(
exists
$state
->{identifiers}{
$base_uri
}) {
return
E(
$state
,
'duplicate anchor uri "%s" found (original at path "%s")'
,
$uri
,
$state
->{identifiers}{
$base_uri
}{anchors}{
$anchor
}{path})
if
exists
((
$state
->{identifiers}{
$base_uri
}{anchors}//{})->{
$anchor
});
$state
->{identifiers}{
$base_uri
}{anchors}{
$anchor
} = {
canonical_uri
=>
$canonical_uri
,
path
=>
$state
->{traversed_schema_path}.
$state
->{schema_path},
};
}
else
{
my
$base_path
=
''
;
if
(
my
$fragment
=
$canonical_uri
->fragment) {
return
E(
$state
,
'something is wrong; "%s" is not the suffix of "%s"'
,
$fragment
,
$state
->{traversed_schema_path}.
$state
->{schema_path})
if
substr
(
$state
->{traversed_schema_path}.
$state
->{schema_path}, -
length
(
$fragment
))
ne
$fragment
;
$base_path
=
substr
(
$state
->{traversed_schema_path}.
$state
->{schema_path}, 0, -
length
(
$fragment
));
}
$state
->{identifiers}{
$base_uri
} = {
canonical_uri
=>
$base_uri
,
path
=>
$base_path
,
specification_version
=>
$state
->{spec_version},
vocabularies
=>
$state
->{vocabularies},
configs
=>
$state
->{configs},
anchors
=> {
$anchor
=> {
canonical_uri
=>
$canonical_uri
,
path
=>
$state
->{traversed_schema_path}.
$state
->{schema_path},
},
},
};
}
return
1;
}
sub
_traverse_keyword_recursiveAnchor (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'boolean'
);
return
E(
$state
,
'"$recursiveAnchor" keyword used without "$id"'
)
if
length
(
$state
->{schema_path});
return
1;
}
sub
_eval_keyword_recursiveAnchor (
$class
,
$data
,
$schema
,
$state
) {
return
1
if
not
$schema
->{
'$recursiveAnchor'
} or
exists
$state
->{recursive_anchor_uri};
$state
->{recursive_anchor_uri} = canonical_uri(
$state
);
return
1;
}
*_traverse_keyword_dynamicAnchor
= \
&_traverse_keyword_anchor
;
sub
_traverse_keyword_ref (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'string'
)
or not assert_uri_reference(
$state
,
$schema
);
return
1;
}
sub
_eval_keyword_ref (
$class
,
$data
,
$schema
,
$state
) {
my
$uri
= Mojo::URL->new(
$schema
->{
'$ref'
})->to_abs(
$state
->{initial_schema_uri});
$class
->eval_subschema_at_uri(
$data
,
$schema
,
$state
,
$uri
);
}
*_traverse_keyword_recursiveRef
= \
&_traverse_keyword_ref
;
sub
_eval_keyword_recursiveRef (
$class
,
$data
,
$schema
,
$state
) {
my
$uri
= Mojo::URL->new(
$schema
->{
'$recursiveRef'
})->to_abs(
$state
->{initial_schema_uri});
my
$schema_info
=
$state
->{evaluator}->_fetch_from_uri(
$uri
);
abort(
$state
,
'EXCEPTION: unable to find resource "%s"'
,
$uri
)
if
not
$schema_info
;
abort(
$state
,
'EXCEPTION: bad reference to "%s": not a schema'
,
$schema_info
->{canonical_uri})
if
$schema_info
->{document}->get_entity_at_location(
$schema_info
->{document_path}) ne
'schema'
;
if
(is_plain_hashref(
$schema_info
->{schema})
and is_type(
'boolean'
,
$schema_info
->{schema}{
'$recursiveAnchor'
})
and
$schema_info
->{schema}{
'$recursiveAnchor'
}) {
$uri
= Mojo::URL->new(
$schema
->{
'$recursiveRef'
})
->to_abs(
$state
->{recursive_anchor_uri} //
$state
->{initial_schema_uri});
}
return
$class
->eval_subschema_at_uri(
$data
,
$schema
,
$state
,
$uri
);
}
*_traverse_keyword_dynamicRef
= \
&_traverse_keyword_ref
;
sub
_eval_keyword_dynamicRef (
$class
,
$data
,
$schema
,
$state
) {
my
$uri
= Mojo::URL->new(
$schema
->{
'$dynamicRef'
})->to_abs(
$state
->{initial_schema_uri});
my
$schema_info
=
$state
->{evaluator}->_fetch_from_uri(
$uri
);
abort(
$state
,
'EXCEPTION: unable to find resource "%s"'
,
$uri
)
if
not
$schema_info
;
abort(
$state
,
'EXCEPTION: bad reference to "%s": not a schema'
,
$schema_info
->{canonical_uri})
if
$schema_info
->{document}->get_entity_at_location(
$schema_info
->{document_path}) ne
'schema'
;
if
(
length
$uri
->fragment
and is_plain_hashref(
$schema_info
->{schema})
and
exists
$schema_info
->{schema}{
'$dynamicAnchor'
}
and
$uri
->fragment eq (
my
$anchor
=
$schema_info
->{schema}{
'$dynamicAnchor'
})) {
foreach
my
$base_scope
(
$state
->{dynamic_scope}->@*) {
my
$test_uri
= Mojo::URL->new(
$base_scope
)->fragment(
$anchor
);
my
$dynamic_anchor_subschema_info
=
$state
->{evaluator}->_fetch_from_uri(
$test_uri
);
if
(
defined
$dynamic_anchor_subschema_info
and (
$dynamic_anchor_subschema_info
->{schema}{
'$dynamicAnchor'
}//
''
) eq
$anchor
) {
$uri
=
$test_uri
;
last
;
}
}
}
return
$class
->eval_subschema_at_uri(
$data
,
$schema
,
$state
,
$uri
);
}
sub
_traverse_keyword_vocabulary (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'object'
);
return
E(
$state
,
'$vocabulary can only appear at the schema resource root'
)
if
length
(
$state
->{schema_path});
my
$valid
= 1;
my
@vocabulary_classes
;
foreach
my
$uri
(
sort
keys
$schema
->{
'$vocabulary'
}->%*) {
if
(not is_type(
'boolean'
,
$schema
->{
'$vocabulary'
}{
$uri
})) {
()= E({
%$state
,
_schema_path_suffix
=>
$uri
},
'$vocabulary value at "%s" is not a boolean'
,
$uri
);
$valid
= 0;
next
;
}
$valid
= 0
if
not assert_uri({
%$state
,
_schema_path_suffix
=>
$uri
},
undef
,
$uri
);
}
return
$valid
;
}
sub
_traverse_keyword_comment (
$class
,
$schema
,
$state
) {
return
if
not assert_keyword_type(
$state
,
$schema
,
'string'
);
return
1;
}
sub
_traverse_keyword_definitions {
shift
->traverse_object_schemas(
@_
) }
sub
_traverse_keyword_defs {
shift
->traverse_object_schemas(
@_
) }
1;