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 =item * Ivan Putintsev =item * Anton Fedotov =item * Denis Ibaev =item * Andrey Khozov =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