File Coverage

blib/lib/JSON/RPC2/Client.pm
Criterion Covered Total %
statement 121 121 100.0
branch 72 72 100.0
condition 34 34 100.0
subroutine 18 18 100.0
pod 10 10 100.0
total 255 255 100.0


line stmt bran cond sub pod time code
1             package JSON::RPC2::Client;
2 14     14   257334 use 5.010001;
  14         73  
3 14     14   53 use warnings;
  14         15  
  14         328  
4 14     14   49 use strict;
  14         16  
  14         221  
5 14     14   5416 use utf8;
  14         101  
  14         57  
6 14     14   323 use Carp;
  14         16  
  14         1003  
7              
8             our $VERSION = 'v2.1.1';
9              
10 14     14   461 use JSON::MaybeXS;
  14         4937  
  14         607  
11 14     14   51 use Scalar::Util qw( weaken refaddr );
  14         17  
  14         13459  
12              
13              
14             sub new {
15 12     12 1 187 my ($class) = @_;
16 12         66 my $self = {
17             next_id => 0,
18             free_id => [],
19             call => {},
20             id => {},
21             };
22 12         32 return bless $self, $class;
23             }
24              
25             sub batch {
26 11     11 1 2303 my ($self, @requests) = @_;
27 11         14 my @call = grep {ref} @requests;
  45         52  
28 11         13 @requests = grep {!ref} @requests;
  45         48  
29 11 100       49 croak 'at least one request required' if !@requests;
30 9         28 my $request = '['.join(q{,}, @requests).']';
31 9         26 return ($request, @call);
32             }
33              
34             sub notify {
35 11     11 1 10212 my ($self, $method, @params) = @_;
36 11 100       47 croak 'method required' if !defined $method;
37 10 100       111 return encode_json({
38             jsonrpc => '2.0',
39             method => $method,
40             (!@params ? () : (
41             params => \@params,
42             )),
43             });
44             }
45              
46             sub notify_named {
47 13     13 1 3360 my ($self, $method, @params) = @_;
48 13 100       46 croak 'method required' if !defined $method;
49 12 100       40 croak 'odd number of elements in %params' if @params % 2;
50 10         22 my %params = @params;
51 10 100       105 return encode_json({
52             jsonrpc => '2.0',
53             method => $method,
54             (!@params ? () : (
55             params => \%params,
56             )),
57             });
58             }
59              
60             sub call {
61 44     44 1 20508 my ($self, $method, @params) = @_;
62 44 100       128 croak 'method required' if !defined $method;
63 42         77 my ($id, $call) = $self->_get_id();
64 42 100       529 my $request = encode_json({
65             jsonrpc => '2.0',
66             method => $method,
67             (!@params ? () : (
68             params => \@params,
69             )),
70             id => $id,
71             });
72 42 100       200 return wantarray ? ($request, $call) : $request;
73             }
74              
75             sub call_named {
76 28     28 1 17076 my ($self, $method, @params) = @_;
77 28 100       93 croak 'method required' if !defined $method;
78 26 100       76 croak 'odd number of elements in %params' if @params % 2;
79 24         59 my %params = @params;
80 24         42 my ($id, $call) = $self->_get_id();
81 24 100       203 my $request = encode_json({
82             jsonrpc => '2.0',
83             method => $method,
84             (!@params ? () : (
85             params => \%params,
86             )),
87             id => $id,
88             });
89 24 100       122 return wantarray ? ($request, $call) : $request;
90             }
91              
92             sub _get_id {
93 66     66   67 my $self = shift;
94 66 100       55 my $id = @{$self->{free_id}} ? pop @{$self->{free_id}} : $self->{next_id}++;
  66         181  
  12         25  
95 66         78 my $call = {};
96 66         316 $self->{call}{ refaddr($call) } = $call;
97 66         115 $self->{id}{ $id } = $call;
98 66         149 weaken($self->{id}{ $id });
99 66         118 return ($id, $call);
100             }
101              
102             sub pending {
103 6     6 1 43 my ($self) = @_;
104 6         4 return values %{ $self->{call} };
  6         45  
105             }
106              
107             sub cancel {
108 13     13 1 4179 my ($self, $call) = @_;
109 13 100       100 croak 'no such request' if !delete $self->{call}{ refaddr($call) };
110 9         20 return;
111             }
112              
113             sub batch_response {
114 18     18 1 9225 my ($self, $json) = @_;
115 18 100       71 croak 'require 1 param' if @_ != 2;
116              
117 16         10 undef $@;
118 16 100       53 my $response = ref $json ? $json : eval { decode_json($json) };
  15         118  
119 16 100       29 if ($@) {
120 5         18 return [ 'Parse error' ];
121             }
122 11 100 100     65 if ($response && ref $response eq 'HASH') {
123 4         8 return [ $self->response($response) ];
124             }
125 7 100 100     35 if (!$response || ref $response ne 'ARRAY') {
126 2         13 return [ 'expect Array or Object' ];
127             }
128 5 100       6 if (!@{$response}) {
  5         10  
129 1         3 return [ 'empty Array' ];
130             }
131              
132 4         4 return map {[ $self->response($_) ]} @{$response};
  5         8  
  4         5  
133             }
134              
135             sub response { ## no critic (ProhibitExcessComplexity RequireArgUnpacking)
136 80     80 1 18577 my ($self, $json) = @_;
137 80 100       205 croak 'require 1 param' if @_ != 2;
138              
139 78         76 undef $@;
140 78 100       142 my $response = ref $json ? $json : eval { decode_json($json) };
  65         330  
141 78 100       134 if ($@) {
142 5         12 return 'Parse error';
143             }
144 73 100       126 if (ref $response ne 'HASH') {
145 3         9 return 'expect Object';
146             }
147 70 100 100     288 if (!defined $response->{jsonrpc} || $response->{jsonrpc} ne '2.0') {
148 11         36 return 'expect {jsonrpc}="2.0"';
149             }
150 59 100 100     270 if (!exists $response->{id} || ref $response->{id} || !defined $response->{id}) {
      100        
151 9         29 return 'expect {id} is scalar';
152             }
153 50 100       110 if (!exists $self->{id}{ $response->{id} }) {
154 4         15 return 'unknown {id}';
155             }
156 46 100 100     170 if (!(exists $response->{result} xor exists $response->{error})) {
157 2         6 return 'expect {result} or {error}';
158             }
159 44 100       74 if (exists $response->{error}) {
160 26         22 my $e = $response->{error};
161 26 100       52 if (ref $e ne 'HASH') {
162 6         18 return 'expect {error} is Object';
163             }
164 20 100 100     117 if (!defined $e->{code} || ref $e->{code} || $e->{code} !~ /\A-?\d+\z/xms) {
      100        
165 8         24 return 'expect {error}{code} is Integer';
166             }
167 12 100 100     43 if (!defined $e->{message} || ref $e->{message}) {
168 6         21 return 'expect {error}{message} is String';
169             }
170             ## no critic (ProhibitMagicNumbers)
171 6 100 100     7 if ((3 == keys %{$e} && !exists $e->{data}) || 3 < keys %{$e}) {
  6   100     31  
  5         19  
172 2         7 return 'only optional key must be {error}{data}';
173             }
174             }
175              
176 22         29 my $id = $response->{id};
177 22         21 push @{ $self->{free_id} }, $id;
  22         47  
178 22         33 my $call = delete $self->{id}{ $id };
179 22 100       52 if ($call) {
180 20         81 $call = delete $self->{call}{ refaddr($call) };
181             }
182 22 100       47 if (!$call) {
183 4         16 return; # call was canceled
184             }
185 18         75 return (undef, $response->{result}, $response->{error}, $call);
186             }
187              
188              
189             1; # Magic true value required at end of module
190             __END__