File Coverage

blib/lib/JSONSchema/Validator/URIResolver.pm
Criterion Covered Total %
statement 91 97 93.8
branch 27 36 75.0
condition 18 25 72.0
subroutine 17 18 94.4
pod 0 9 0.0
total 153 185 82.7


line stmt bran cond sub pod time code
1             package JSONSchema::Validator::URIResolver;
2              
3             # ABSTRACT: URI resolver
4              
5 6     6   42 use strict;
  6         14  
  6         198  
6 6     6   31 use warnings;
  6         14  
  6         191  
7 6     6   32 use Carp 'croak';
  6         10  
  6         363  
8              
9 6     6   34 use Scalar::Util 'weaken';
  6         12  
  6         200  
10              
11 6     6   30 use URI;
  6         11  
  6         106  
12 6     6   25 use URI::Escape;
  6         14  
  6         247  
13 6     6   2911 use Encode;
  6         53915  
  6         471  
14              
15 6     6   50 use JSONSchema::Validator::JSONPointer 'json_pointer';
  6         10  
  6         255  
16 6     6   37 use JSONSchema::Validator::Util qw(get_resource decode_content);
  6         11  
  6         5642  
17              
18             # what keys contain the schema? Required to find an $id in a schema
19             my $SEARCH_ID = {
20             value => {
21             additionalItems => 1,
22             items => 1,
23             additionalProperties => 1,
24             not => 1,
25             propertyNames => 1,
26             contains => 1,
27             if => 1,
28             then => 1,
29             else => 1
30             },
31             kv_value => {
32             properties => 1,
33             patternProperties => 1,
34             dependencies => 1,
35             definitions => 1
36             },
37             arr_value => {
38             items => 1,
39             allOf => 1,
40             anyOf => 1,
41             oneOf => 1
42             }
43             };
44              
45             sub new {
46 321     321 0 1105 my ($class, %params) = @_;
47              
48 321 50       726 croak 'URIResolver: validator must be specified' unless $params{validator};
49 321 50       613 croak 'URIResolver: schema must be specified' unless defined $params{schema};
50              
51 321         499 my $validator = $params{validator};
52 321         422 my $schema = $params{schema};
53 321   50     604 my $base_uri = $params{base_uri} // '';
54              
55 321   50     578 my $scheme_handlers = $params{scheme_handlers} // {};
56              
57 321         1038 weaken($validator);
58              
59 321         952 my $self = {
60             validator => $validator,
61             cache => {
62             $base_uri => $schema
63             },
64             scheme_handlers => $scheme_handlers
65             };
66              
67 321         560 bless $self, $class;
68              
69 321 100       830 if ('#' eq substr $base_uri, -1) {
70 21         63 $base_uri = substr $base_uri, 0, - 1;
71 21         55 $self->{cache}{$base_uri} = $schema;
72             }
73              
74 321 100 100     742 $self->cache_id(URI->new($base_uri), $schema) if $validator->using_id_with_ref && ref $schema eq 'HASH';
75              
76 321         1182 return $self;
77             }
78              
79 5243     5243 0 13155 sub validator { shift->{validator} }
80 0     0 0 0 sub scheme_handlers { shift->{scheme_handlers} }
81 1381     1381 0 5273 sub cache { shift->{cache} }
82              
83             # self - URIResolver
84             # origin_uri - URI
85             # return (scope|string, schema)
86             sub resolve {
87 454     454 0 806 my ($self, $origin_uri) = @_;
88              
89 454 100       827 return ($origin_uri->as_string, $self->cache->{$origin_uri->as_string}) if exists $self->cache->{$origin_uri->as_string};
90              
91 142         906 my $uri = $origin_uri->clone;
92 142         826 $uri->fragment(undef);
93              
94 142         1675 my $schema = $self->cache_resolve($uri);
95 142         732 return $self->fragment_resolve($origin_uri, $schema);
96             }
97              
98             # self - URIResolver
99             # uri - URI
100             # return schema
101             sub cache_resolve {
102 142     142 0 295 my ($self, $uri) = @_;
103              
104 142         267 my $scheme = $uri->scheme;
105              
106 142 50       1515 return $self->cache->{$uri->as_string} if exists $self->cache->{$uri->as_string};
107              
108 0         0 my ($response, $mime_type) = get_resource($self->scheme_handlers, $uri->as_string);
109 0         0 my $schema = decode_content($response, $mime_type, $uri->as_string);
110              
111 0         0 $self->cache->{$uri->as_string} = $schema;
112              
113 0 0       0 $self->cache_id($uri, $schema) if $self->validator->using_id_with_ref;
114              
115 0         0 return $schema;
116             }
117              
118             # self - URIResolver
119             # uri - URI
120             # schema - HASH/ARRAY
121             # return (scope|string, schema)
122             sub fragment_resolve {
123 142     142 0 266 my ($self, $uri, $schema) = @_;
124 142 50       223 return ($uri->as_string, $self->cache->{$uri->as_string}) if exists $self->cache->{$uri->as_string};
125              
126 142         799 my $enc = Encode::find_encoding("UTF-8");
127 142         4415 my $fragment = $enc->decode(uri_unescape($uri->fragment), 1);
128              
129 142         3149 my $pointer = json_pointer->new(
130             scope => $uri->as_string,
131             value => $schema,
132             validator => $self->validator
133             );
134              
135             # try to use fragment as json pointer
136 142         377 $pointer = $pointer->get($fragment);
137 142         299 my $subschema = $pointer->value;
138 142         249 my $current_scope = $pointer->scope;
139              
140 142         287 $self->cache->{$uri->as_string} = $subschema;
141              
142 142         956 return ($current_scope, $subschema);
143             }
144              
145             # self - URIResolver
146             # uri - URI
147             # schema - HASH/ARRAY
148             sub cache_id {
149 280     280 0 22814 my ($self, $uri, $schema) = @_;
150              
151             # try to find id/$id and cache it to properly handle links in $ref
152             # https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref
153              
154 280         487 my $scopes = [$uri];
155 280         638 $self->cache_id_dfs($schema, $scopes);
156             }
157              
158             # self - URIResolver
159             # schema - HASH/ARRAY
160             # scopes - [URI, ...]
161             sub cache_id_dfs {
162 3504     3504 0 4843 my ($self, $schema, $scopes) = @_;
163 3504 50       5305 return unless ref $schema eq 'HASH';
164              
165             # skip all fields (id field too) if $ref present (draft 4-7)
166 3504 100       6389 return if exists $schema->{'$ref'};
167              
168 2480 100 66     3595 if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
169 47         100 my $id = URI->new($schema->{$self->validator->ID_FIELD});
170 47         4051 my $scope = $scopes->[-1];
171              
172 47 50 33     359 $id = ($scope && $scope->as_string) ? $id->abs($scope) : $id;
173              
174 47         3778 $self->cache->{$id->as_string} = $schema;
175 47         255 push @$scopes, $id;
176             }
177              
178 2480         5466 for my $k (keys %$schema) {
179 4313 100 100     7974 if ($SEARCH_ID->{value}{$k} && ref $schema->{$k} eq 'HASH') {
180 405         689 $self->cache_id_dfs($schema->{$k}, $scopes);
181             }
182              
183 4313 100 100     7303 if ($SEARCH_ID->{arr_value}{$k} && ref $schema->{$k} eq 'ARRAY') {
184 289         364 for my $value (@{$schema->{$k}}) {
  289         469  
185 609 50       969 next unless ref $value eq 'HASH';
186 609         895 $self->cache_id_dfs($value, $scopes);
187             }
188             }
189              
190 4313 100 66     7999 if ($SEARCH_ID->{kv_value}{$k} && ref $schema->{$k} eq 'HASH') {
191 513         653 for my $kv_key (keys %{$schema->{$k}}) {
  513         1436  
192 2245         2999 my $value = $schema->{$k}{$kv_key};
193 2245 100       3643 next unless ref $value eq 'HASH';
194 2210         3089 $self->cache_id_dfs($value, $scopes);
195             }
196             }
197             }
198              
199 2480 100 66     3871 if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
200 47         112 pop @$scopes;
201             }
202             }
203              
204             1;
205              
206             __END__