package JSONSchema::Validator::JSONPointer;
# ABSTRACT: JSON Pointer with URI resolving
use strict;
use warnings;
use URI;
use Carp 'croak';
use Scalar::Util 'weaken';
use overload
'bool' => sub { $_[0]->value },
fallback => 1;
our @ISA = 'Exporter';
our @EXPORT_OK = qw(json_pointer);
sub json_pointer {
return __PACKAGE__;
}
sub append {
my ($class, $path, @values) = @_;
my $suffix = join('/', map { $class->escape($_) } @values);
return $path =~ m!/$!
? $path . $suffix
: $path . '/' . $suffix;
}
sub join {
my ($class, @parts) = @_;
return '/' . join('/', map { $class->escape($_) } @parts);
}
sub escape {
my ($class, $value) = @_;
$value =~ s!~!~0!g;
$value =~ s!/!~1!g;
return $value;
}
sub unescape {
my ($class, $value) = @_;
$value =~ s!~1!/!g;
$value =~ s!~0!~!g;
return $value;
}
sub new {
my ($class, %params) = @_;
my ($scope, $value, $validator) = @params{qw/scope value validator/};
croak 'JSONPointer: scope is required' unless defined $scope;
croak 'JSONPointer: validator is required' unless $validator;
weaken($validator);
my $self = {
scope => $scope,
value => $value,
validator => $validator
};
bless $self, $class;
return $self;
}
sub validator { shift->{validator} }
sub scope { shift->{scope} }
sub value { shift->{value} }
sub xget {
my ($self, @parts) = @_;
my $current_scope = $self->scope;
my $current_value = $self->value;
while (ref $current_value eq 'HASH' && $current_value->{'$ref'}) {
my $ref = URI->new($current_value->{'$ref'});
$ref = $ref->abs($current_scope) if $current_scope;
($current_scope, $current_value) = $self->validator->resolver->resolve($ref);
}
if (ref $current_value eq 'HASH' && $self->validator->using_id_with_ref) {
my $id = $current_value->{$self->validator->ID_FIELD};
if ($id && !ref $id) {
$current_scope = $current_scope
? URI->new($id)->abs($current_scope)->as_string
: $id;
}
}
for my $part (@parts) {
if (ref $current_value eq 'HASH' && exists $current_value->{$part}) {
$current_value = $current_value->{$part};
} elsif (ref $current_value eq 'ARRAY' && $part =~ m/^\d+$/ && scalar(@$current_value) > $part) {
$current_value = $current_value->[$part];
} else {
$current_value = undef;
last;
}
while (ref $current_value eq 'HASH' && $current_value->{'$ref'}) {
my $ref = URI->new($current_value->{'$ref'});
$ref = $ref->abs($current_scope) if $current_scope;
($current_scope, $current_value) = $self->validator->resolver->resolve($ref);
}
if (ref $current_value eq 'HASH' && $self->validator->using_id_with_ref) {
my $id = $current_value->{$self->validator->ID_FIELD};
if ($id && !ref $id) {
$current_scope = $current_scope
? URI->new($id)->abs($current_scope)->as_string
: $id;
}
}
}
return __PACKAGE__->new(
value => $current_value,
scope => $current_scope,
validator => $self->validator
)
}
sub get {
# pointer is string which is already urldecoded and utf8-decoded
my ($self, $pointer) = @_;
return $self unless $pointer;
croak "Invalid JSON Pointer $pointer" unless $pointer =~ s!^/!!;
my @parts = length $pointer
? map { $self->unescape($_) } split(/\//, $pointer, -1)
: ('');
return $self->xget(@parts);
}
sub keys {
my ($self, %params) = @_;
my $raw = $params{raw} // 0;
if (ref $self->value eq 'HASH') {
return map { $raw ? $_ : $self->join($_) } keys %{$self->value};
}
if (ref $self->value eq 'ARRAY') {
return map { $raw ? $_ : $self->join($_) } 0 .. $#{$self->value};
}
return;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
JSONSchema::Validator::JSONPointer - JSON Pointer with URI resolving
=head1 VERSION
version 0.011
=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