File Coverage

blib/lib/JSON/RPC2/AnyEvent/Server.pm
Criterion Covered Total %
statement 89 98 90.8
branch 26 38 68.4
condition 11 12 91.6
subroutine 21 21 100.0
pod 3 3 100.0
total 150 172 87.2


line stmt bran cond sub pod time code
1             package JSON::RPC2::AnyEvent::Server;
2 5     5   198997 use 5.010;
  5         48  
3 5     5   26 use strict;
  5         9  
  5         95  
4 5     5   22 use warnings;
  5         9  
  5         218  
5              
6             our $VERSION = "0.03";
7              
8 5     5   3017 use AnyEvent;
  5         15764  
  5         144  
9 5     5   30 use Carp 'croak';
  5         10  
  5         252  
10 5     5   46 use Scalar::Util 'reftype';
  5         11  
  5         202  
11 5     5   2413 use Try::Tiny;
  5         9842  
  5         286  
12              
13 5     5   1972 use JSON::RPC2::AnyEvent::Constants qw(:all);
  5         10  
  5         6342  
14              
15              
16             sub new {
17 4     4 1 360 my $class = shift;
18 4         14 my $self = bless {}, $class;
19 4         17 while ( @_ ) {
20 8         21 my $method = shift;
21 8         11 my $spec = shift;
22 8 100 100     51 if ( (reftype $spec // '') eq 'CODE' ) {
23 5         20 $self->register($method, $spec);
24             } else {
25 3         8 $self->register($method, $spec, shift);
26             }
27             }
28 4         11 $self;
29             }
30              
31             sub dispatch {
32 11     11 1 15285 my $self = shift;
33 11         18 my $json = shift;
34 11         247 my $ret_cv = AE::cv;
35             try{
36 11     11   547 my $type = _check_format($json); # die when $json's format is invalid
37 9         24 my $method = $self->{$json->{method}};
38 9 50       21 unless ( $method ) { # Method not found
39 0         0 $ret_cv->send(_make_error_response($json->{id}, ERR_METHOD_NOT_FOUND, 'Method not found'));
40 0         0 return $ret_cv;
41             }
42 9 50       21 if ( $type eq 'c' ) { # RPC call
43             $method->(AE::cv{
44 9         3001585 my $cv = shift;
45             try{
46 9         554 $ret_cv->send(_make_response($json->{id}, $cv->recv));
47             } catch {
48 0         0 $ret_cv->send(_make_error_response($json->{id}, ERR_SERVER_ERROR, 'Server error', shift));
49 9         104 };
50 9         196 }, $json->{params});
51 9         211 return $ret_cv;
52             } else { # Notification request (no response)
53 0         0 $method->(AE::cv, $json->{params}); # pass dummy cv
54 0         0 return undef;
55             }
56             } catch { # Invalid request
57 2 50   2   164 my $err = _make_error_response((reftype $json eq 'HASH' ? $json->{id} : undef), ERR_INVALID_REQUEST, 'Invalid Request', shift);
58 2         13 $ret_cv->send($err);
59 2         67 return $ret_cv;
60 11         8756 };
61             }
62              
63             sub _check_format {
64             # Returns
65             # "c" : when the value represents rpc call
66             # "n" : when the value represents notification
67             # croak: when the value is in invalid format
68 11     11   23 my $json = shift;
69 11 50       42 reftype $json eq 'HASH' or croak "JSON-RPC request MUST be an Object (hash)";
70             #$json->{jsonrpc} eq "2.0" or croak "Unsupported JSON-RPC version"; # This module supports only JSON-RPC 2.0 spec, but here just ignores this member.
71 11 100 66     250 exists $json->{method} && not ref $json->{method} or croak "`method' MUST be a String value";
72 10 100       26 if ( exists $json->{params} ) {
73 9 100 100     213 (reftype $json->{params} // '') eq 'ARRAY' || (reftype $json->{params} // '') eq 'HASH' or croak "`params' MUST be an array or an object";
      100        
      100        
74             } else {
75 1         3 $json->{params} = [];
76             }
77 9 50       22 return 'n' unless exists $json->{id};
78 9 50       26 not ref $json->{id} or croak "`id' MUST be neighter an array nor an object";
79 9         18 return 'c';
80             }
81              
82             sub _make_response {
83 9     9   108 my ($id, $result) = @_;
84             {
85 9         52 jsonrpc => '2.0',
86             id => $id,
87             result => $result,
88             };
89             }
90              
91             sub _make_error_response {
92 2     2   6 my ($id, $code, $msg, $data) = @_;
93             {
94 2 50       14 jsonrpc => '2.0',
95             id => $id,
96             error => {
97             code => $code,
98             message => "$msg",
99             (defined $data ? (data => $data) : ()),
100             },
101             };
102             }
103              
104              
105             sub register {
106 8     8 1 15 my $self = shift;
107 8         16 my ($method, $spec, $code) = @_;
108 8 100       28 if ( UNIVERSAL::isa($spec, "CODE") ) { # spec is omitted.
109 5         8 $code = $spec;
110 5     5   23 $spec = sub{ $_[0] };
  5         19  
111             } else {
112 3         7 $spec = _parse_argspec($spec);
113 3 50       9 croak "`$code' is not CODE ref" unless UNIVERSAL::isa($code, 'CODE');
114             }
115             $self->{$method} = sub{
116 9     9   67 my ($cv, $params) = @_;
117 9         21 $code->($cv, $spec->($params), $params);
118 8         42 };
119 8         26 $self;
120             }
121              
122             sub _parse_argspec {
123 3     3   4 my $orig = my $spec = shift;
124 3 100       16 if ( $spec =~ s/^\s*\[\s*// ) { # Wants array
    50          
125 1 50       6 croak "Invalid argspec. Unmatched '[' in argspec: $orig" unless $spec =~ s/\s*\]\s*$//;
126 1         6 my @parts = split /\s*,\s*/, $spec;
127             return sub{
128 1     1   1 my $params = shift;
129 1 50       6 return $params if UNIVERSAL::isa($params, 'ARRAY');
130             # Got a hash! Then, convert it to an array!
131 0         0 my $args = [];
132 0         0 push @$args, $params->{$_} foreach @parts;
133 0         0 return $args;
134 1         5 };
135             } elsif ( $spec =~ s/\s*\{\s*// ) { # Wants hash
136 2 50       8 croak "Invalid argspec. Unmatched '{' in argspec: $orig" unless $spec =~ s/\s*\}\s*$//;
137 2         11 my @parts = split /\s*,\s*/, $spec;
138             return sub{
139 3     3   5 my $params = shift;
140 3 100       10 return $params if UNIVERSAL::isa($params, 'HASH');
141             # Got an array! Then, convert it to a hash!
142 2         3 my $args = {};
143 2         6 for ( my $i=0; $i < @parts; $i++ ) {
144 8         20 $args->{$parts[$i]} = $params->[$i];
145             }
146 2         5 return $args;
147 2         10 };
148             } else {
149 0           croak "Invalid argspec. Argspec must be enclosed in [] or {}: $orig";
150             }
151             }
152              
153              
154              
155             1;
156             __END__