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   50 use strict;
  6         19  
  6         222  
6 6     6   36 use warnings;
  6         11  
  6         223  
7 6     6   33 use Carp 'croak';
  6         12  
  6         320  
8              
9 6     6   46 use Scalar::Util 'weaken';
  6         13  
  6         233  
10              
11 6     6   37 use URI;
  6         13  
  6         126  
12 6     6   37 use URI::Escape;
  6         12  
  6         292  
13 6     6   5237 use Encode;
  6         61472  
  6         500  
14              
15 6     6   51 use JSONSchema::Validator::JSONPointer 'json_pointer';
  6         16  
  6         284  
16 6     6   37 use JSONSchema::Validator::Util qw(get_resource decode_content);
  6         15  
  6         6776  
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 1197 my ($class, %params) = @_;
47              
48 321 50       901 croak 'URIResolver: validator must be specified' unless $params{validator};
49 321 50       739 croak 'URIResolver: schema must be specified' unless defined $params{schema};
50              
51 321         624 my $validator = $params{validator};
52 321         591 my $schema = $params{schema};
53 321   50     901 my $base_uri = $params{base_uri} // '';
54              
55 321   50     723 my $scheme_handlers = $params{scheme_handlers} // {};
56              
57 321         1352 weaken($validator);
58              
59 321         1244 my $self = {
60             validator => $validator,
61             cache => {
62             $base_uri => $schema
63             },
64             scheme_handlers => $scheme_handlers
65             };
66              
67 321         670 bless $self, $class;
68              
69 321 100       923 if ('#' eq substr $base_uri, -1) {
70 21         46 $base_uri = substr $base_uri, 0, - 1;
71 21         53 $self->{cache}{$base_uri} = $schema;
72             }
73              
74 321 100 100     954 $self->cache_id(URI->new($base_uri), $schema) if $validator->using_id_with_ref && ref $schema eq 'HASH';
75              
76 321         1314 return $self;
77             }
78              
79 5243     5243 0 15906 sub validator { shift->{validator} }
80 0     0 0 0 sub scheme_handlers { shift->{scheme_handlers} }
81 1373     1373 0 6378 sub cache { shift->{cache} }
82              
83             # self - URIResolver
84             # origin_uri - URI
85             # return (scope|string, schema)
86             sub resolve {
87 450     450 0 967 my ($self, $origin_uri) = @_;
88              
89 450 100       997 return ($origin_uri->as_string, $self->cache->{$origin_uri->as_string}) if exists $self->cache->{$origin_uri->as_string};
90              
91 142         1089 my $uri = $origin_uri->clone;
92 142         1096 $uri->fragment(undef);
93              
94 142         2168 my $schema = $self->cache_resolve($uri);
95 142         905 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 292 my ($self, $uri) = @_;
103              
104 142         378 my $scheme = $uri->scheme;
105              
106 142 50       1887 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 325 my ($self, $uri, $schema) = @_;
124 142 50       303 return ($uri->as_string, $self->cache->{$uri->as_string}) if exists $self->cache->{$uri->as_string};
125              
126 142         1012 my $enc = Encode::find_encoding("UTF-8");
127 142         5476 my $fragment = $enc->decode(uri_unescape($uri->fragment), 1);
128              
129 142         4042 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         495 $pointer = $pointer->get($fragment);
137 142         420 my $subschema = $pointer->value;
138 142         326 my $current_scope = $pointer->scope;
139              
140 142         359 $self->cache->{$uri->as_string} = $subschema;
141              
142 142         1203 return ($current_scope, $subschema);
143             }
144              
145             # self - URIResolver
146             # uri - URI
147             # schema - HASH/ARRAY
148             sub cache_id {
149 280     280 0 28604 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         657 my $scopes = [$uri];
155 280         699 $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 5864 my ($self, $schema, $scopes) = @_;
163 3504 50       6599 return unless ref $schema eq 'HASH';
164              
165             # skip all fields (id field too) if $ref present (draft 4-7)
166 3504 100       7003 return if exists $schema->{'$ref'};
167              
168 2480 100 66     4331 if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
169 47         119 my $id = URI->new($schema->{$self->validator->ID_FIELD});
170 47         4362 my $scope = $scopes->[-1];
171              
172 47 50 33     434 $id = ($scope && $scope->as_string) ? $id->abs($scope) : $id;
173              
174 47         4732 $self->cache->{$id->as_string} = $schema;
175 47         290 push @$scopes, $id;
176             }
177              
178 2480         6859 for my $k (keys %$schema) {
179 4313 100 100     9960 if ($SEARCH_ID->{value}{$k} && ref $schema->{$k} eq 'HASH') {
180 405         888 $self->cache_id_dfs($schema->{$k}, $scopes);
181             }
182              
183 4313 100 100     9052 if ($SEARCH_ID->{arr_value}{$k} && ref $schema->{$k} eq 'ARRAY') {
184 289         427 for my $value (@{$schema->{$k}}) {
  289         607  
185 609 50       1260 next unless ref $value eq 'HASH';
186 609         1127 $self->cache_id_dfs($value, $scopes);
187             }
188             }
189              
190 4313 100 66     9716 if ($SEARCH_ID->{kv_value}{$k} && ref $schema->{$k} eq 'HASH') {
191 513         801 for my $kv_key (keys %{$schema->{$k}}) {
  513         1775  
192 2245         3659 my $value = $schema->{$k}{$kv_key};
193 2245 100       4413 next unless ref $value eq 'HASH';
194 2210         3790 $self->cache_id_dfs($value, $scopes);
195             }
196             }
197             }
198              
199 2480 100 66     4556 if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
200 47         140 pop @$scopes;
201             }
202             }
203              
204             1;
205              
206             __END__