File Coverage

blib/lib/JSON/Patch.pm
Criterion Covered Total %
statement 56 63 88.8
branch 32 40 80.0
condition 5 6 83.3
subroutine 10 10 100.0
pod 2 2 100.0
total 105 121 86.7


line stmt bran cond sub pod time code
1             package JSON::Patch;
2              
3 5     5   223501 use 5.006;
  5         33  
4 5     5   24 use strict;
  5         5  
  5         97  
5 5     5   19 use warnings FATAL => 'all';
  5         7  
  5         157  
6 5     5   1339 use parent 'Exporter';
  5         943  
  5         20  
7              
8 5     5   224 use Carp qw(croak);
  5         8  
  5         214  
9 5     5   1581 use Struct::Diff 0.96;
  5         39362  
  5         232  
10 5     5   1926 use Struct::Path 0.82 qw(path);
  5         8432  
  5         335  
11 5     5   1782 use Struct::Path::JsonPointer 0.04 qw(path2str str2path);
  5         4876  
  5         2915  
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.03
33              
34             =cut
35              
36             our $VERSION = '0.03';
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 2 100   2 1 422 my @stask = Struct::Diff::list_diff @_ == 2
78             ? Struct::Diff::diff($_[0], $_[1], noO => 1, noU => 1, trimR => 1)
79             : $_[0];
80              
81 2         462 my ($hunk, @patch, $path);
82              
83 2         6 while (@stask) {
84 3         40 ($path, $hunk) = splice @stask, -2, 2;
85              
86 3 100       4 if (exists ${$hunk}->{A}) {
  3 50       7  
    50          
87 2         4 push @patch, {op => 'add', value => ${$hunk}->{A}};
  2         6  
88 1         2 } elsif (exists ${$hunk}->{N}) {
89 0         0 push @patch, {op => 'replace', value => ${$hunk}->{N}};
  0         0  
90 1         14 } elsif (exists ${$hunk}->{R}) {
91 0         0 push @patch, {op => 'remove'};
92             } else {
93 1         5 next;
94             }
95              
96 2         7 $patch[-1]->{path} = path2str($path);
97             }
98              
99 2         49 return \@patch;
100             }
101              
102             =head2 patch
103              
104             Apply patch.
105              
106             patch($target, $patch);
107              
108             =cut
109              
110             sub patch($;$) {
111 12 100   12 1 6774 croak "Arrayref expected for patch" unless (ref $_[1] eq 'ARRAY');
112              
113 11         14 for my $hunk (@{$_[1]}) {
  11         22  
114 11 100       105 croak "Hashref expected for patch item" unless (ref $hunk eq 'HASH');
115 10 100       98 croak "Undefined op value" unless (defined $hunk->{op});
116 9 100       100 croak "Path parameter missing" unless (exists $hunk->{path});
117              
118 8 100       10 my $path = eval { str2path($hunk->{path}) }
  8         23  
119             or croak "Failed to parse 'path' pointer";
120              
121 7 100 66     256 if ($hunk->{op} eq 'add' or $hunk->{op} eq 'replace') {
    100 100        
    100          
    100          
122 2 100       97 croak "Value parameter missing" unless (exists $hunk->{value});
123             path(
124             $_[0],
125             $path,
126             assign => $hunk->{value},
127             expand => 1,
128 1         5 insert => $hunk->{op} eq 'add',
129             strict => 1,
130             );
131              
132             } elsif ($hunk->{op} eq 'remove') {
133 1 50       13 eval { path($_[0], $path, delete => 1) } or
  1         5  
134             croak "Path does not exist";
135              
136             } elsif ($hunk->{op} eq 'move' or $hunk->{op} eq 'copy') {
137 2 100       4 my $from = eval { str2path($hunk->{from}) } or
  2         4  
138             croak "Failed to parse 'from' pointer";
139             my @found = path(
140             $_[0],
141             $from,
142 1         39 delete => $hunk->{op} eq 'move',
143             deref => 1
144             );
145 1 50       134 croak "Source path does not exist" unless (@found);
146              
147 0         0 path($_[0], $path, assign => $found[0], expand => 1);
148              
149             } elsif ($hunk->{op} eq 'test') {
150 1 50       3 croak "Value parameter missing" unless (exists $hunk->{value});
151 1 50       4 my @found = path($_[0], $path, deref => 1) or
152             croak "Path does not exist";
153 0         0 my $diff = Struct::Diff::diff($found[0], $hunk->{value}, noU => 1);
154 0 0       0 croak "Test failed" if (keys %{$diff});
  0         0  
155              
156             } else {
157 1         103 croak "Unsupported op '$hunk->{op}'";
158             }
159             }
160             }
161              
162             =head1 AUTHOR
163              
164             Michael Samoglyadov, C<< >>
165              
166             =head1 BUGS
167              
168             Please report any bugs or feature requests to C,
169             or through the web interface at
170             L. I will be
171             notified, and then you'll automatically be notified of progress on your bug as
172             I make changes.
173              
174             =head1 SUPPORT
175              
176             You can find documentation for this module with the perldoc command.
177              
178             perldoc JSON::Patch
179              
180             You can also look for information at:
181              
182             =over 4
183              
184             =item * RT: CPAN's request tracker (report bugs here)
185              
186             L
187              
188             =item * AnnoCPAN: Annotated CPAN documentation
189              
190             L
191              
192             =item * CPAN Ratings
193              
194             L
195              
196             =item * Search CPAN
197              
198             L
199              
200             =back
201              
202             =head1 SEE ALSO
203              
204             L,
205             L, L
206              
207             =head1 LICENSE AND COPYRIGHT
208              
209             Copyright 2018 Michael Samoglyadov.
210              
211             This program is free software; you can redistribute it and/or modify it under
212             the terms of either: the GNU General Public License as published by the Free
213             Software Foundation; or the Artistic License.
214              
215             See L for more information.
216              
217             =cut
218              
219             1; # End of JSON::Patch