File Coverage

blib/lib/JSON/RPC2/TwoWay.pm
Criterion Covered Total %
statement 84 98 85.7
branch 34 56 60.7
condition 20 30 66.6
subroutine 21 23 91.3
pod 4 4 100.0
total 163 211 77.2


line stmt bran cond sub pod time code
1             package JSON::RPC2::TwoWay;
2 2     2   71657 use 5.10.0;
  2         16  
3 2     2   9 use strict;
  2         3  
  2         37  
4 2     2   7 use warnings;
  2         3  
  2         70  
5              
6             our $VERSION = '0.06'; # VERSION
7              
8             # standard perl
9 2     2   8 use Carp;
  2         4  
  2         142  
10 2     2   898 use Data::Dumper;
  2         6223  
  2         142  
11              
12             # cpan
13 2     2   13 use JSON::MaybeXS qw();
  2         4  
  2         40  
14              
15             # us
16 2     2   840 use JSON::RPC2::TwoWay::Connection;
  2         5  
  2         60  
17              
18 2     2   13 use constant ERR_NOTNOT => -32000;
  2         2  
  2         99  
19 2     2   8 use constant ERR_ERR => -32001;
  2         3  
  2         72  
20 2     2   10 use constant ERR_BADSTATE => -32002;
  2         2  
  2         73  
21 2     2   9 use constant ERR_REQ => -32600;
  2         3  
  2         80  
22 2     2   11 use constant ERR_METHOD => -32601;
  2         3  
  2         71  
23 2     2   16 use constant ERR_PARAMS => -32602;
  2         4  
  2         76  
24 2     2   10 use constant ERR_PARSE => -32700;
  2         4  
  2         1761  
25              
26             sub new {
27 1     1 1 84 my ($class, %opt) = @_;
28             my $self = {
29             debug => $opt{debug} // 0,
30 1   50     11 json => $opt{json} // JSON::MaybeXS->new(utf8 => 1),
      33        
31             methods => {},
32             };
33 1         24 return bless $self, $class;
34             }
35              
36             sub newconnection {
37 2     2 1 1039 my ($self, %opt) = @_;
38             my $conn = JSON::RPC2::TwoWay::Connection->new(
39             rpc => $self,
40             owner => $opt{owner},
41             write => $opt{write},
42             debug => $self->{debug},
43             json => $self->{json},
44 2         14 );
45 1         2 return $conn
46             }
47              
48             sub register {
49 7     7 1 2574 my ($self, $name, $cb, %opts) = @_;
50 7         22 my %defaults = (
51             by_name => 1,
52             non_blocking => 0,
53             notification => 0,
54             raw => 0,
55             state => undef,
56             );
57 7 50       18 croak 'no self?' unless $self;
58 7 50       13 croak 'no name?' unless $name;
59 7 100       171 croak 'no callback?' unless ref $cb eq 'CODE';
60 6         20 %opts = (%defaults, %opts);
61             croak 'a non_blocking notification is not sensible'
62 6 100 100     79 if $opts{non_blocking} and $opts{notification};
63 5 100       74 croak "method $name already registered" if $self->{methods}->{$name};
64             $self->{methods}->{$name} = {
65             name => $name,
66             cb => $cb,
67             by_name => $opts{by_name},
68             non_blocking => $opts{non_blocking},
69             notification => $opts{notification},
70             raw => $opts{raw},
71             state => $opts{state},
72 4         22 };
73             }
74              
75             sub unregister {
76 0     0 1 0 my ($self, $name) = @_;
77 0 0       0 croak 'no self?' unless $self;
78 0 0       0 croak 'no name?' unless $name;
79 0         0 my $method = delete $self->{methods}->{$name};
80 0 0       0 croak "method $name already registered" unless $method;
81             }
82              
83              
84             sub _handle_request {
85 9     9   16 my ($self, $c, $r) = @_;
86 9 50       17 say STDERR ' in handle_request' if $self->{debug};
87             #print Dumper(\@_);
88 9         15 my $m = $self->{methods}->{$r->{method}};
89 9         14 my $id = $r->{id};
90 9 100       16 return $self->_error($c, $id, ERR_METHOD, 'Method not found.') unless $m;
91 8 100 100     27 return $self->_error($c, $id, ERR_NOTNOT, 'Method is not a notification.') if !$id and !$m->{notification};
92              
93             return $self->_error($c, $id, ERR_REQ, 'Invalid Request: params should be array or object.')
94 7 100 100     23 if ref $r->{params} ne 'ARRAY' and ref $r->{params} ne 'HASH';
95              
96             return $self->_error($c, $id, ERR_PARAMS, 'This method expects '.($m->{by_name} ? 'named' : 'positional').' params.')
97 6 100       23 if ref $r->{params} ne ($m->{by_name} ? 'HASH' : 'ARRAY');
    100          
    100          
98            
99             return $self->_error($c, $id, ERR_BADSTATE, 'This method requires connection state ' . ($m->{state} // 'undef'))
100 4 100 50     14 if $m->{state} and not ($c->state and $m->{state} eq $c->state);
      66        
      100        
101              
102 3 50       7 if ($m->{raw}) {
103 0         0 my $cb;
104 0 0   0   0 $cb = sub { $c->write($self->{json}->encode($_[0])) if $id } if $m->{non_blocking};
  0 0       0  
105              
106 0         0 local $@;
107             #my @ret = eval { $m->{cb}->($c, $jsonr, $r, $cb)};
108 0         0 my @ret = eval { $m->{cb}->($c, $r, $cb)};
  0         0  
109 0 0       0 return $self->_error($c, $id, ERR_ERR, "Method threw error: $@") if $@;
110             #say STDERR 'method returned: ', Dumper(\@ret);
111              
112 0 0 0     0 $c->write($self->{json}->encode($ret[0])) if !$cb and $id;
113             return
114 0         0 }
115              
116 3         3 my $cb;
117 3 50   1   11 $cb = sub { $self->_result($c, $id, \@_) if $id; } if $m->{non_blocking};
  1 100       662  
118              
119 3         3 local $@;
120 3         4 my @ret = eval { $m->{cb}->($c, $r->{params}, $cb)};
  3         7  
121 3 50       15 return $self->_error($c, $id, ERR_ERR, "Method threw error: $@") if $@;
122             #say STDERR 'method returned: ', Dumper(\@ret);
123            
124 3 100 66     12 return $self->_result($c, $id, \@ret) if !$cb and $id;
125 1         4 return;
126             }
127              
128             sub _error {
129 11     11   24 my ($self, $c, $id, $code, $message, $data) = @_;
130 11   50     32 my $err = "error: $code " . $message // '';
131 11 50       19 say STDERR $err if $self->{debug};
132             $c->write($self->{json}->encode({
133 11 50       101 jsonrpc => '2.0',
134             id => $id,
135             error => {
136             code => $code,
137             message => $message,
138             (defined $data ? ( data => $data ) : ()),
139             },
140             }));
141 11         73 return 0, $err;
142             }
143              
144             sub _result {
145 3     3   7 my ($self, $c, $id, $result) = @_;
146 3 100       9 $result = $$result[0] if scalar(@$result) == 1;
147             #say STDERR Dumper($result) if $self->{debug};
148             $c->write($self->{json}->encode({
149 3         28 jsonrpc => '2.0',
150             id => $id,
151             result => $result,
152             }));
153 3         20 return;
154             }
155              
156             #sub DESTROY {
157             # my $self = shift;
158             # say 'destroying ', $self;
159             #}
160              
161             1;
162              
163             =encoding utf8
164              
165             =head1 NAME
166              
167             JSON::RPC2::TwoWay - Transport-independent bidirectional JSON-RPC 2.0
168              
169             =head1 SYNOPSIS
170              
171             $rpc = JSON::RPC2::TwoWay->new();
172             $rpc->register('ping', \&handle_ping);
173              
174             $con = $rpc->newconnection(
175             owner => $owner,
176             write => sub { $stream->write(@_) }
177             );
178             @err = $con->handle($stream->read);
179             die $err[-1] if @err;
180              
181             =head1 DESCRIPTION
182              
183             L is a base class to implement bidirectional (a.k.a.
184             twoway) communication using JSON-RPC 2.0 remote procedure calls: both sides
185             can operate as Clients and Servers simultaneously. This class is
186             transport-independent.
187              
188             =head1 METHODS
189              
190             =head2 new
191              
192             $rpc = JSON::RPC2::TwoWay->new();
193              
194             Class method that returns a new JSON::RPC2::TwoWay object.
195              
196             Valid arguments are:
197              
198             =over 4
199              
200             =item - debug: print debugging to STDERR
201              
202             =item - json: json encoder/decoder object to use. Defaults to JSON::MaybeXS->new().
203              
204             =back
205              
206             =head2 newconnection
207              
208             my $con = $rpc->newconnection(owner => $owner, write = $write);
209              
210             Creates a L with owner $owner and writer $write.
211              
212             See L for details.
213              
214             =head2 register
215              
216             $rpc->register('my_method', sub { ... }, option => ... );
217              
218             Register a new method to be callable. Calls are passed to the callback.
219              
220             Valid options are:
221              
222             =over 4
223              
224             =item - by_name
225              
226             When true the arguments to the method will be passed in as a hashref,
227             otherwise as a arrayref. (default true)
228              
229             =item - non_blocking
230              
231             When true the method callback will receive a callback as its last argument
232             for passing back the results (default false)
233              
234             =item - notification
235              
236             When true the method is a notification and no return value is expected by
237             the caller. (Any returned values will be discarded in the handler.)
238              
239             =item - state
240              
241             When defined must be a string value defining the state the connection (see
242             L) must be in for this call to be accepted.
243              
244             =back
245              
246             =head2 unregister
247              
248             $rpc->unregister('my_method')
249              
250             Unregister a method.
251              
252             =head1 REGISTERED CALLBACK CALLING CONVENTION
253              
254             The method callback passed as the second argument of register is called with
255             2 or 3 arguments: the first argument is the JSON::RPC2::TwoWay::Connection
256             object on which the request came in. The second argument is a arrayref or
257             hashref depending on if the method was registered as by-position or by-name.
258             The third argument, if present is a result callback that needs to be called
259             with the results of the method:
260              
261             sub mymethod {
262             ($c, $i, $cb) = @_;
263             $foo = $i->{foo};
264             }
265              
266             some time later;
267              
268             $cb->("you sent $foo");
269              
270             If the method callback returns a scalar value the JSON-RPC 2.0 result member
271             value will be a JSON string, number, or null value. If the method returns a
272             hashref the result member value will be an object. If the method returns
273             multiple values or an arrayref the result member value will be an array.
274              
275             =head1 SEE ALSO
276              
277             =over
278              
279             =item *
280              
281             L
282              
283             =item *
284              
285             L: JSON-RPC 2.0 Specification
286              
287             =back
288              
289             =head1 ACKNOWLEDGEMENT
290              
291             This software has been developed with support from L.
292             In German: Diese Software wurde mit Unterstützung von L entwickelt.
293              
294             =head1 AUTHORS
295              
296             =over 4
297              
298             =item *
299              
300             Wieger Opmeer
301              
302             =back
303              
304             =head1 COPYRIGHT AND LICENSE
305              
306             This software is copyright (c) 2016-2019 by Wieger Opmeer.
307              
308             This is free software; you can redistribute it and/or modify it under
309             the same terms as the Perl 5 programming language system itself.
310              
311             =cut
312