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   46 use strict;
  6         14  
  6         207  
6 6     6   33 use warnings;
  6         12  
  6         192  
7 6     6   31 use Carp 'croak';
  6         14  
  6         305  
8              
9 6     6   35 use Scalar::Util 'weaken';
  6         13  
  6         212  
10              
11 6     6   32 use URI;
  6         13  
  6         113  
12 6     6   32 use URI::Escape;
  6         11  
  6         252  
13 6     6   4740 use Encode;
  6         61598  
  6         483  
14              
15 6     6   51 use JSONSchema::Validator::JSONPointer 'json_pointer';
  6         12  
  6         253  
16 6     6   37 use JSONSchema::Validator::Util qw(get_resource decode_content);
  6         14  
  6         6393  
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 1233 my ($class, %params) = @_;
47              
48 321 50       950 croak 'URIResolver: validator must be specified' unless $params{validator};
49 321 50       771 croak 'URIResolver: schema must be specified' unless defined $params{schema};
50              
51 321         564 my $validator = $params{validator};
52 321         520 my $schema = $params{schema};
53 321   50     778 my $base_uri = $params{base_uri} // '';
54              
55 321   50     734 my $scheme_handlers = $params{scheme_handlers} // {};
56              
57 321         1261 weaken($validator);
58              
59 321         1133 my $self = {
60             validator => $validator,
61             cache => {
62             $base_uri => $schema
63             },
64             scheme_handlers => $scheme_handlers
65             };
66              
67 321         639 bless $self, $class;
68              
69 321 100       961 if ('#' eq substr $base_uri, -1) {
70 21         57 $base_uri = substr $base_uri, 0, - 1;
71 21         61 $self->{cache}{$base_uri} = $schema;
72             }
73              
74 321 100 100     938 $self->cache_id(URI->new($base_uri), $schema) if $validator->using_id_with_ref && ref $schema eq 'HASH';
75              
76 321         1316 return $self;
77             }
78              
79 5243     5243 0 14236 sub validator { shift->{validator} }
80 0     0 0 0 sub scheme_handlers { shift->{scheme_handlers} }
81 1373     1373 0 5867 sub cache { shift->{cache} }
82              
83             # self - URIResolver
84             # origin_uri - URI
85             # return (scope|string, schema)
86             sub resolve {
87 450     450 0 969 my ($self, $origin_uri) = @_;
88              
89 450 100       939 return ($origin_uri->as_string, $self->cache->{$origin_uri->as_string}) if exists $self->cache->{$origin_uri->as_string};
90              
91 142         976 my $uri = $origin_uri->clone;
92 142         931 $uri->fragment(undef);
93              
94 142         1877 my $schema = $self->cache_resolve($uri);
95 142         772 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 273 my ($self, $uri) = @_;
103              
104 142         306 my $scheme = $uri->scheme;
105              
106 142 50       1585 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 294 my ($self, $uri, $schema) = @_;
124 142 50       297 return ($uri->as_string, $self->cache->{$uri->as_string}) if exists $self->cache->{$uri->as_string};
125              
126 142         904 my $enc = Encode::find_encoding("UTF-8");
127 142         4973 my $fragment = $enc->decode(uri_unescape($uri->fragment), 1);
128              
129 142         3566 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         444 $pointer = $pointer->get($fragment);
137 142         324 my $subschema = $pointer->value;
138 142         285 my $current_scope = $pointer->scope;
139              
140 142         326 $self->cache->{$uri->as_string} = $subschema;
141              
142 142         1090 return ($current_scope, $subschema);
143             }
144              
145             # self - URIResolver
146             # uri - URI
147             # schema - HASH/ARRAY
148             sub cache_id {
149 280     280 0 27219 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         590 my $scopes = [$uri];
155 280         729 $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 5257 my ($self, $schema, $scopes) = @_;
163 3504 50       6238 return unless ref $schema eq 'HASH';
164              
165             # skip all fields (id field too) if $ref present (draft 4-7)
166 3504 100       6358 return if exists $schema->{'$ref'};
167              
168 2480 100 66     3969 if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
169 47         116 my $id = URI->new($schema->{$self->validator->ID_FIELD});
170 47         4559 my $scope = $scopes->[-1];
171              
172 47 50 33     416 $id = ($scope && $scope->as_string) ? $id->abs($scope) : $id;
173              
174 47         4470 $self->cache->{$id->as_string} = $schema;
175 47         279 push @$scopes, $id;
176             }
177              
178 2480         5852 for my $k (keys %$schema) {
179 4313 100 100     8772 if ($SEARCH_ID->{value}{$k} && ref $schema->{$k} eq 'HASH') {
180 405         756 $self->cache_id_dfs($schema->{$k}, $scopes);
181             }
182              
183 4313 100 100     8106 if ($SEARCH_ID->{arr_value}{$k} && ref $schema->{$k} eq 'ARRAY') {
184 289         402 for my $value (@{$schema->{$k}}) {
  289         506  
185 609 50       1081 next unless ref $value eq 'HASH';
186 609         945 $self->cache_id_dfs($value, $scopes);
187             }
188             }
189              
190 4313 100 66     8703 if ($SEARCH_ID->{kv_value}{$k} && ref $schema->{$k} eq 'HASH') {
191 513         669 for my $kv_key (keys %{$schema->{$k}}) {
  513         1565  
192 2245         3318 my $value = $schema->{$k}{$kv_key};
193 2245 100       3950 next unless ref $value eq 'HASH';
194 2210         3435 $self->cache_id_dfs($value, $scopes);
195             }
196             }
197             }
198              
199 2480 100 66     4165 if (exists $schema->{$self->validator->ID_FIELD} && !ref $schema->{$self->validator->ID_FIELD}) {
200 47         133 pop @$scopes;
201             }
202             }
203              
204             1;
205              
206             __END__