File Coverage

blib/lib/JSON/Pointer.pm
Criterion Covered Total %
statement 161 169 95.2
branch 73 100 73.0
condition 14 18 77.7
subroutine 23 24 95.8
pod 10 10 100.0
total 281 321 87.5


line stmt bran cond sub pod time code
1             package JSON::Pointer;
2              
3 11     11   651171 use 5.008_001;
  11         49  
  11         479  
4 11     11   65 use strict;
  11         23  
  11         373  
5 11     11   63 use warnings;
  11         28  
  11         850  
6              
7 11     11   66 use B;
  11         27  
  11         681  
8 11     11   62 use Carp qw(croak);
  11         23  
  11         623  
9 11     11   9867 use Clone qw(clone);
  11         44998  
  11         1247  
10 11     11   3858 use JSON qw(encode_json decode_json);
  11         76146  
  11         90  
11 11     11   9334 use JSON::Pointer::Context;
  11         39  
  11         397  
12 11     11   7650 use JSON::Pointer::Exception qw(:all);
  11         31  
  11         2208  
13 11     11   7556 use JSON::Pointer::Syntax qw(is_array_numeric_index);
  11         32  
  11         2106  
14 11     11   16094 use URI::Escape qw(uri_unescape);
  11         23004  
  11         39471  
15              
16             our $VERSION = '0.04';
17              
18             sub traverse {
19 140     140 1 50703 my ($class, $document, $pointer, $strict) = @_;
20 140 50       434 $strict = 1 unless defined $strict;
21 140         570 $pointer = uri_unescape($pointer);
22              
23 140         2188 my @tokens = JSON::Pointer::Syntax->tokenize($pointer);
24 140         1448 my $context = JSON::Pointer::Context->new(+{
25             pointer => $pointer,
26             tokens => \@tokens,
27             target => $document,
28             parent => $document,
29             });
30              
31 140         407 foreach my $token (@tokens) {
32 181         704 $context->begin($token);
33              
34 181         561 my $parent = $context->parent;
35 181         1106 my $type = ref $parent;
36              
37 181 100       722 if ($type eq "HASH") {
    50          
38 129 100       559 unless (exists $parent->{$token}) {
39 24         107 return _throw_or_return(ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE, $context, $strict);
40             }
41              
42 105         403 $context->next($parent->{$token});
43 105         272 next;
44             }
45             elsif ($type eq "ARRAY") {
46 52         79 my $elements_length = $#{$parent} + 1;
  52         130  
47              
48 52 100 100     189 if (is_array_numeric_index($token) && $token <= $elements_length) {
    100          
49 40         168 $context->next($parent->[$token]);
50 40         120 next;
51             }
52             elsif ($token eq "-") {
53 1         6 $context->next(undef);
54 1         3 next;
55             }
56             else {
57 11         50 return _throw_or_return(ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE, $context, $strict);
58             }
59             }
60             else {
61 0         0 return _throw_or_return(ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE, $context, $strict);
62             }
63             }
64              
65 105         391 $context->result(1);
66 105         930 return $context;
67             }
68              
69             sub get {
70 33     33 1 40858 my ($class, $document, $pointer, $strict) = @_;
71 33 100       107 $strict = 0 unless defined $strict;
72              
73 33         38 my $context;
74 33         49 eval {
75 33         96 $context = $class->traverse($document, $pointer, $strict);
76             };
77 33 100       387 if (my $e = $@) {
78 3         72 croak $e;
79             }
80              
81 30 100       93 return $context->result ? $context->target : undef;
82             }
83              
84             sub contains {
85 9     9 1 10368 my ($class, $document, $pointer) = @_;
86 9         30 my $context = $class->traverse($document, $pointer, 0);
87 9         25 return $context->result;
88             }
89              
90             sub add {
91 34     34 1 64787 my ($class, $document, $pointer, $value) = @_;
92              
93 34         480 my $patched_document = clone($document);
94              
95 34         246 my $context = $class->traverse($patched_document, $pointer, 0);
96 34         103 my $parent = $context->parent;
97 34         173 my $type = ref $parent;
98              
99 34 100       116 if ($type eq "HASH") {
    50          
100 21 100 100     145 if (!$context->result && @{$context->processed_tokens} < @{$context->tokens} - 1) {
  19         6624  
  19         163  
101             ### Parent isn't object
102 2         19 JSON::Pointer::Exception->throw(
103             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
104             context => $context,
105             );
106             }
107              
108 19 100       189 if (defined $context->last_token) {
109 18         133 $parent->{$context->last_token} = $value;
110             }
111             else {
112             ### pointer is empty string (whole document)
113 1         7 $patched_document = $value;
114             }
115              
116 19         301 return $patched_document;
117             }
118             elsif ($type eq "ARRAY") {
119 13 100       39 unless ($context->result) {
120 3         21 JSON::Pointer::Exception->throw(
121             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
122             context => $context,
123             );
124             }
125              
126 10 50       77 if (defined $context->last_token) {
127 10         55 my $parent_array_length = $#{$parent} + 1;
  10         24  
128 10 100       30 my $target_index = ($context->last_token eq "-") ?
129             $parent_array_length : $context->last_token;
130              
131 10         112 splice(@$parent, $target_index, 0, $value);
132             }
133             else {
134 0         0 $patched_document = $value;
135             }
136              
137 10         104 return $patched_document;
138             }
139             else {
140 0 0       0 unless ($context->result) {
141 0         0 JSON::Pointer::Exception->throw(
142             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
143             context => $context,
144             );
145             }
146              
147 0         0 return $value;
148             }
149             }
150              
151             sub remove {
152 13     13 1 37939 my ($class, $document, $pointer) = @_;
153              
154 13         223 my $patched_document = clone($document);
155              
156 13         64 my $context = $class->traverse($patched_document, $pointer, 1);
157 13         40 my $parent = $context->parent;
158 13         73 my $type = ref $parent;
159              
160 13 100       49 if ($type eq "HASH") {
    100          
161 8         103 my $target_member = $context->last_token;
162 8 100       54 if (defined $target_member) {
163 7         21 my $removed = delete $parent->{$target_member};
164 7 50       83 return wantarray ? ($patched_document, $removed) : $patched_document;
165             }
166             else {
167             ### pointer is empty string (whole document)
168 1 50       9 return wantarray ? (undef, $patched_document) : undef;
169             }
170             }
171             elsif ($type eq "ARRAY") {
172 4         17 my $target_index = $context->last_token;
173 4 100       28 if (defined $target_index) {
174 3         6 my $parent_array_length = $#{$parent} + 1;
  3         10  
175 3 50       14 $target_index = $parent_array_length if ($target_index eq "-");
176 3         8 my $removed = splice(@$parent, $target_index, 1);
177 3 50       29 return wantarray ? ($patched_document, $removed) : $patched_document;
178             }
179             else {
180             ### pointer is empty string (whole document)
181 1 50       11 return wantarray ? (undef, $patched_document) : undef;
182             }
183             }
184             else {
185 1 50       5 unless ($context->result) {
186 0         0 JSON::Pointer::Exception->throw(
187             code => ERROR_POINTER_REFERENCES_NON_EXISTENT_VALUE,
188             context => $context,
189             );
190             }
191              
192 1 50       18 return wantarray ? (undef, $patched_document) : undef;
193             }
194             }
195              
196             sub replace {
197 12     12 1 64200 my ($class, $document, $pointer, $value) = @_;
198              
199 12         148 my $patched_document = clone($document);
200 12         59 my $context = $class->traverse($patched_document, $pointer, 1);
201 12         37 my $parent = $context->parent;
202 12         63 my $type = ref $parent;
203              
204 12 100       107 if ($type eq "HASH") {
205 4         13 my $target_member = $context->last_token;
206 4 100       28 if (defined $target_member) {
207 3         9 my $replaced = $parent->{$context->last_token};
208 3         21 $parent->{$context->last_token} = $value;
209 3 50       45 return wantarray ? ($patched_document, $replaced) : $patched_document;
210             }
211             else {
212             ### pointer is empty string (whole document)
213 1 50       11 return wantarray ? ($value, $patched_document) : $value;
214             }
215             }
216             else {
217 8         95 my $target_index = $context->last_token;
218 8 100       47 if (defined $target_index) {
219 6         8 my $parent_array_length = $#{$parent} + 1;
  6         15  
220 6 50       18 $target_index = $parent_array_length if ($target_index eq "-");
221 6         12 my $replaced = $parent->[$target_index];
222 6         13 $parent->[$target_index] = $value;
223 6 50       46 return wantarray ? ($patched_document, $replaced) : $patched_document;
224             }
225             else {
226             ### pointer is empty string (whole document)
227 2 50       13 return wantarray ? ($value, $patched_document) : $value;
228             }
229             }
230             }
231              
232             sub set {
233 0     0 1 0 shift->replace(@_);
234             }
235              
236             sub copy {
237 1     1 1 1762 my ($class, $document, $from_pointer, $to_pointer) = @_;
238 1         7 my $context = $class->traverse($document, $from_pointer, 1);
239 1         4 return $class->add($document, $to_pointer, $context->target);
240             }
241              
242             sub move {
243 5     5 1 15105 my ($class, $document, $from_pointer, $to_pointer) = @_;
244 5         25 my ($patched_document, $removed) = $class->remove($document, $from_pointer);
245 5         19 $class->add($patched_document, $to_pointer, $removed);
246             }
247              
248             sub test {
249 23     23 1 55145 my ($class, $document, $pointer, $value) = @_;
250              
251 23         76 my $context = $class->traverse($document, $pointer, 0);
252              
253 23 100       60 return 0 unless $context->result;
254              
255 22         136 my $target = $context->target;
256 22         93 my $target_type = ref $target;
257              
258 22 100 100     108 if ($target_type eq "HASH" || $target_type eq "ARRAY") {
    100          
259 4 100       56 return encode_json($target) eq encode_json($value) ? 1 : 0;
260             }
261             elsif (defined $target) {
262 17 50 100     59 if (JSON::is_bool($target)) {
    100 66        
    100          
263 0 0 0     0 return JSON::is_bool($value) && $target == $value ? 1 : 0;
264             }
265             elsif (_is_iv_or_nv($target) && _is_iv_or_nv($value)) {
266 12 50       75 return $target == $value ? 1 : 0;
267             }
268             elsif (_is_pv($target) && _is_pv($value)) {
269 4 100       47 return $target eq $value ? 1 : 0;
270             }
271             else {
272 1         6 return 0;
273             }
274             }
275             else {
276             ### null
277 1 50       9 return !defined $value ? 1 : 0;
278             }
279             }
280              
281             sub _throw_or_return {
282 35     35   70 my ($code, $context, $strict) = @_;
283              
284 35 100       89 if ($strict) {
285 3         21 JSON::Pointer::Exception->throw(
286             code => $code,
287             context => $context,
288             );
289             }
290             else {
291 32         139 $context->last_error($code);
292 32         332 return $context;
293             }
294             }
295              
296             sub _is_iv_or_nv {
297 30     30   137 my $value = shift;
298 30         166 my $flags = B::svref_2object(\$value)->FLAGS;
299 30 0       204 return ($flags & ( B::SVp_IOK | B::SVp_NOK )) and !($flags & B::SVp_POK);
300             }
301              
302             sub _is_pv {
303 9     9   15 my $value = shift;
304 9         37 my $flags = B::svref_2object(\$value)->FLAGS;
305 9 0       44 return !($flags & ( B::SVp_IOK | B::SVp_NOK )) and ($flags & B::SVp_POK);
306             }
307              
308             1;
309              
310             __END__