package JSONSchema::Validator::URIResolver;
# ABSTRACT: URI resolver
use strict;
use warnings;
use Carp 'croak';
use Scalar::Util 'weaken';
use URI;
use URI::Escape;
use Encode;
use JSONSchema::Validator::JSONPointer 'json_pointer';
use JSONSchema::Validator::Util qw(get_resource decode_content);
# what keys contain the schema? Required to find an $id in a schema
my $SEARCH_ID = {
value => {
additionalItems => 1,
items => 1,
additionalProperties => 1,
not => 1,
propertyNames => 1,
contains => 1,
if => 1,
then => 1,
else => 1
},
kv_value => {
properties => 1,
patternProperties => 1,
dependencies => 1,
definitions => 1
},
arr_value => {
items => 1,
allOf => 1,
anyOf => 1,
oneOf => 1
}
};
sub new {
my ($class, %params) = @_;
croak 'URIResolver: validator must be specified' unless $params{validator};
croak 'URIResolver: schema must be specified' unless defined $params{schema};
my $validator = $params{validator};
my $schema = $params{schema};
my $base_uri = $params{base_uri} // '';
my $scheme_handlers = $params{scheme_handlers} // {};
weaken($validator);
my $self = {
validator => $validator,
cache => {
$base_uri => $schema
},
scheme_handlers => $scheme_handlers
};
bless $self, $class;
if ('#' eq substr $base_uri, -1) {
$base_uri = substr $base_uri, 0, - 1;
$self->{cache}{$base_uri} = $schema;
}
$self->cache_id(URI->new($base_uri), $schema) if $validator->using_id_with_ref && ref $schema eq 'HASH';
return $self;
}
sub validator { shift->{validator} }
sub scheme_handlers { shift->{scheme_handlers} }
sub cache { shift->{cache} }
# self - URIResolver
# origin_uri - URI
# return (scope|string, schema)
sub resolve {
my ($self, $origin_uri) = @_;
return ($origin_uri->as_string, $self->cache->{$origin_uri->as_string}) if exists $self->cache->{$origin_uri->as_string};
my $uri = $origin_uri->clone;
$uri->fragment(undef);
my $schema = $self->cache_resolve($uri);
return $self->fragment_resolve($origin_uri, $schema);
}
# self - URIResolver
# uri - URI
# return schema
sub cache_resolve {
my ($self, $uri) = @_;
my $scheme = $uri->scheme;
return $self->cache->{$uri->as_string} if exists $self->cache->{$uri->as_string};
my ($response, $mime_type) = get_resource($self->scheme_handlers, $uri->as_string);
my $schema = decode_content($response, $mime_type, $uri->as_string);
$self->cache->{$uri->as_string} = $schema;
$self->cache_id($uri, $schema) if $self->validator->using_id_with_ref;
return $schema;
}
# self - URIResolver
# uri - URI
# schema - HASH/ARRAY
# return (scope|string, schema)
sub fragment_resolve {
my ($self, $uri, $schema) = @_;
return ($uri->as_string, $self->cache->{$uri->as_string}) if exists $self->cache->{$uri->as_string};
my $enc = Encode::find_encoding("UTF-8");
my $fragment = $enc->decode(uri_unescape($uri->fragment), 1);
my $pointer = json_pointer->new(
scope => $uri->as_string,
value => $schema,
validator => $self->validator
);
# try to use fragment as json pointer
$pointer = $pointer->get($fragment);
my $subschema = $pointer->value;
my $current_scope = $pointer->scope;
$self->cache->{$uri->as_string} = $subschema;
return ($current_scope, $subschema);
}
# self - URIResolver
# uri - URI
# schema - HASH/ARRAY
sub cache_id {
my ($self, $uri, $schema) = @_;
# try to find id/$id and cache it to properly handle links in $ref
# https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref
my $scopes = [$uri];
$self->cache_id_dfs($schema, $scopes);
}
# self - URIResolver
# schema - HASH/ARRAY
# scopes - [URI, ...]
sub cache_id_dfs {
my ($self, $schema, $scopes) = @_;
return unless ref $schema eq 'HASH';
# skip all fields (id field too) if $ref present (draft 4-7)
return if exists $schema->{'$ref'};
if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
my $id = URI->new($schema->{$self->validator->ID_FIELD});
my $scope = $scopes->[-1];
$id = ($scope && $scope->as_string) ? $id->abs($scope) : $id;
$self->cache->{$id->as_string} = $schema;
push @$scopes, $id;
}
for my $k (keys %$schema) {
if ($SEARCH_ID->{value}{$k} && ref $schema->{$k} eq 'HASH') {
$self->cache_id_dfs($schema->{$k}, $scopes);
}
if ($SEARCH_ID->{arr_value}{$k} && ref $schema->{$k} eq 'ARRAY') {
for my $value (@{$schema->{$k}}) {
next unless ref $value eq 'HASH';
$self->cache_id_dfs($value, $scopes);
}
}
if ($SEARCH_ID->{kv_value}{$k} && ref $schema->{$k} eq 'HASH') {
for my $kv_key (keys %{$schema->{$k}}) {
my $value = $schema->{$k}{$kv_key};
next unless ref $value eq 'HASH';
$self->cache_id_dfs($value, $scopes);
}
}
}
if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
pop @$scopes;
}
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSONSchema::Validator::URIResolver - URI resolver
=head1 VERSION
version 0.010
=head1 AUTHORS
=over 4
=item *
Alexey Stavrov <logioniz@ya.ru>
=item *
Ivan Putintsev <uid@rydlab.ru>
=item *
Anton Fedotov <tosha.fedotov.2000@gmail.com>
=item *
Denis Ibaev <dionys@gmail.com>
=item *
Andrey Khozov <andrey@rydlab.ru>
=back
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2021 by Alexey Stavrov.
This is free software, licensed under:
The MIT (X11) License
=cut