File Coverage

blib/lib/JSON/RPC2/Client.pm
Criterion Covered Total %
statement 120 121 99.1
branch 71 72 98.6
condition 31 34 91.1
subroutine 18 18 100.0
pod 10 10 100.0
total 250 255 98.0


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