File Coverage

blib/lib/JSON/Patch.pm
Criterion Covered Total %
statement 59 64 92.1
branch 33 40 82.5
condition 5 6 83.3
subroutine 10 10 100.0
pod 2 2 100.0
total 109 122 89.3


line stmt bran cond sub pod time code
1             package JSON::Patch;
2              
3 5     5   306364 use 5.006;
  5         54  
4 5     5   24 use strict;
  5         9  
  5         111  
5 5     5   23 use warnings FATAL => 'all';
  5         8  
  5         200  
6 5     5   2001 use parent 'Exporter';
  5         1376  
  5         24  
7              
8 5     5   264 use Carp qw(croak);
  5         9  
  5         243  
9 5     5   2123 use Struct::Diff 0.96;
  5         57993  
  5         292  
10 5     5   2373 use Struct::Path 0.82 qw(path);
  5         10034  
  5         361  
11 5     5   2182 use Struct::Path::JsonPointer 0.04 qw(path2str str2path);
  5         5934  
  5         3176  
12              
13             our @EXPORT_OK = qw(
14             diff
15             patch
16             );
17              
18             =head1 NAME
19              
20             JSON::Patch - JSON Patch (rfc6902) for perl structures
21              
22             =begin html
23              
24             Travis CI
25             Coverage Status
26             CPAN version
27              
28             =end html
29              
30             =head1 VERSION
31              
32             Version 0.04
33              
34             =cut
35              
36             our $VERSION = '0.04';
37              
38             =head1 SYNOPSIS
39              
40             use Test::More tests => 2;
41             use JSON::Patch qw(diff patch);
42              
43             my $old = {foo => ['bar']};
44             my $new = {foo => ['bar', 'baz']};
45              
46             my $patch = diff($old, $new);
47             is_deeply(
48             $patch,
49             [
50             {op => 'add', path => '/foo/1', value => 'baz'}
51             ]
52             );
53              
54             patch($old, $patch);
55             is_deeply($old, $new);
56              
57             =head1 EXPORT
58              
59             Nothing is exported by default.
60              
61             =head1 SUBROUTINES
62              
63             =head2 diff
64              
65             Calculate patch for two arguments:
66              
67             $patch = diff($old, $new);
68              
69             Convert L diff to JSON Patch when single arg passed:
70              
71             require Struct::Diff;
72             $patch = diff(Struct::Diff::diff($old, $new));
73              
74             =cut
75              
76             sub diff($;$) {
77 3 100   3 1 1352 my $diff = @_ == 2
78             ? Struct::Diff::diff($_[0], $_[1], noO => 1, noU => 1, trimR => 1)
79             : $_[0];
80 3         1467 my @stask = Struct::Diff::list_diff($diff, sort => 1);
81              
82 3         230 my ($hunk, @patch, $path);
83              
84 3         9 while (@stask) {
85 13         233 ($path, $hunk) = splice @stask, -2, 2;
86              
87 13 100       19 if (exists ${$hunk}->{A}) {
  13 100       20  
    50          
88 2         5 push @patch, {op => 'add', value => ${$hunk}->{A}};
  2         8  
89 11         25 } elsif (exists ${$hunk}->{N}) {
90 10         11 push @patch, {op => 'replace', value => ${$hunk}->{N}};
  10         20  
91 1         3 } elsif (exists ${$hunk}->{R}) {
92 0         0 push @patch, {op => 'remove'};
93             } else {
94 1         2 next;
95             }
96              
97 12         27 $patch[-1]->{path} = path2str($path);
98             }
99              
100 3         101 return \@patch;
101             }
102              
103             =head2 patch
104              
105             Apply patch.
106              
107             patch($target, $patch);
108              
109             =cut
110              
111             sub patch($$) {
112 12 100   12 1 8413 croak "Arrayref expected for patch" unless (ref $_[1] eq 'ARRAY');
113              
114 11         22 for my $hunk (@{$_[1]}) {
  11         27  
115 11 100       148 croak "Hashref expected for patch item" unless (ref $hunk eq 'HASH');
116 10 100       122 croak "Undefined op value" unless (defined $hunk->{op});
117 9 100       116 croak "Path parameter missing" unless (exists $hunk->{path});
118              
119 8 100       12 my $path = eval { str2path($hunk->{path}) }
  8         29  
120             or croak "Failed to parse 'path' pointer";
121              
122 7 100 66     252 if ($hunk->{op} eq 'add' or $hunk->{op} eq 'replace') {
    100 100        
    100          
    100          
123 2 100       102 croak "Value parameter missing" unless (exists $hunk->{value});
124             path(
125             $_[0],
126             $path,
127             assign => $hunk->{value},
128             expand => 1,
129 1         8 insert => $hunk->{op} eq 'add',
130             strict => 1,
131             );
132              
133             } elsif ($hunk->{op} eq 'remove') {
134 1 50       17 eval { path($_[0], $path, delete => 1) } or
  1         8  
135             croak "Path does not exist";
136              
137             } elsif ($hunk->{op} eq 'move' or $hunk->{op} eq 'copy') {
138 2 100       4 my $from = eval { str2path($hunk->{from}) } or
  2         6  
139             croak "Failed to parse 'from' pointer";
140             my @found = path(
141             $_[0],
142             $from,
143 1         24 delete => $hunk->{op} eq 'move',
144             deref => 1
145             );
146 1 50       131 croak "Source path does not exist" unless (@found);
147              
148 0         0 path($_[0], $path, assign => $found[0], expand => 1);
149              
150             } elsif ($hunk->{op} eq 'test') {
151 1 50       4 croak "Value parameter missing" unless (exists $hunk->{value});
152 1 50       5 my @found = path($_[0], $path, deref => 1) or
153             croak "Path does not exist";
154 0         0 my $diff = Struct::Diff::diff($found[0], $hunk->{value}, noU => 1);
155 0 0       0 croak "Test failed" if (keys %{$diff});
  0         0  
156              
157             } else {
158 1         96 croak "Unsupported op '$hunk->{op}'";
159             }
160             }
161             }
162              
163             =head1 AUTHOR
164              
165             Michael Samoglyadov, C<< >>
166              
167             =head1 BUGS
168              
169             Please report any bugs or feature requests to C,
170             or through the web interface at
171             L. I will be
172             notified, and then you'll automatically be notified of progress on your bug as
173             I make changes.
174              
175             =head1 SUPPORT
176              
177             You can find documentation for this module with the perldoc command.
178              
179             perldoc JSON::Patch
180              
181             You can also look for information at:
182              
183             =over 4
184              
185             =item * RT: CPAN's request tracker (report bugs here)
186              
187             L
188              
189             =item * AnnoCPAN: Annotated CPAN documentation
190              
191             L
192              
193             =item * CPAN Ratings
194              
195             L
196              
197             =item * Search CPAN
198              
199             L
200              
201             =back
202              
203             =head1 SEE ALSO
204              
205             L,
206             L, L
207              
208             =head1 LICENSE AND COPYRIGHT
209              
210             Copyright 2018 Michael Samoglyadov.
211              
212             This program is free software; you can redistribute it and/or modify it under
213             the terms of either: the GNU General Public License as published by the Free
214             Software Foundation; or the Artistic License.
215              
216             See L for more information.
217              
218             =cut
219              
220             1; # End of JSON::Patch