File Coverage

lib/MojoX/JSON/RPC/Dispatcher.pm
Criterion Covered Total %
statement 98 111 88.2
branch 67 84 79.7
condition 36 50 72.0
subroutine 8 8 100.0
pod 1 1 100.0
total 210 254 82.6


line stmt bran cond sub pod time code
1             package MojoX::JSON::RPC::Dispatcher;
2              
3 3     3   20 use Mojo::Base 'Mojolicious::Controller';
  3         4  
  3         15  
4 3     3   489 use Mojo::JSON qw(decode_json);
  3         3  
  3         127  
5 3     3   1245 use MojoX::JSON::RPC::Dispatcher::Method;
  3         7  
  3         20  
6 3     3   91 use MojoX::JSON::RPC::Service;
  3         5  
  3         15  
7              
8             # process JSON-RPC call
9             sub call {
10 37     37 1 309890 my ($self) = @_;
11              
12 37         129 my $rpc_response = $self->_handle_methods( $self->_acquire_methods );
13 37 100       180 if ( !$rpc_response ) { # is notification
14 3         8 $self->tx->res->code(204);
15 3         32 return $self->rendered;
16             }
17 34         84 my $res = $self->tx->res;
18             $res->code(
19             $self->_translate_error_code_to_status(
20             ref $rpc_response eq 'HASH' && exists $rpc_response->{error}
21             ? $rpc_response->{error}->{code}
22 34 100 100     398 : q{}
23             )
24             );
25 34         261 $res->headers->content_type('application/json-rpc');
26 34         554 return $self->render(json => $rpc_response);
27             }
28              
29             sub _acquire_methods {
30 37     37   89 my ($self) = @_;
31              
32 37         113 my $log = $self->app->log;
33 37         251 my $req = $self->req; # Mojo::Message::Request object
34 37         336 my $method = $req->method; # GET / POST
35 37         190 my $request;
36              
37 37 100       138 if ( $method eq 'POST' ) {
    50          
38 35 50       117 if ( $log->is_level('debug') ) {
39 0         0 $log->debug( 'REQUEST: BODY> ' . $req->body );
40             }
41            
42 35         273 my $decode_error;
43 35 100       49 eval{ $request = decode_json( $req->body ); 1; } or $decode_error = $@;
  35         98  
  33         41348  
44 35 100       2208 if ( $decode_error ) {
45 2         15 $log->debug( 'REQUEST: JSON error> ' . $decode_error );
46 2         58 return MojoX::JSON::RPC::Dispatcher::Method->new->parse_error(
47             $decode_error );
48             }
49             }
50             elsif ( $method eq 'GET' ) {
51 2         15 my $params = $req->query_params->to_hash;
52 2         145 my $decoded_params;
53              
54 2 50       8 if ( exists $params->{params} ) {
55 0         0 my $decode_error;
56 0 0       0 eval{ $decoded_params = decode_json( $params->{params} ); 1; } or $decode_error = $@;
  0         0  
  0         0  
57            
58 0 0       0 if ( $decode_error ) {
59 0         0 return MojoX::JSON::RPC::Dispatcher::Method->new->parse_error(
60             $decode_error );
61             }
62             }
63             $request = {
64             method => $params->{method},
65 2 50       14 exists $params->{id} ? ( id => $params->{id} ) : (),
    50          
66             defined $decoded_params ? ( params => $decoded_params ) : ()
67             };
68             }
69             else {
70 0         0 return MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request(
71             qq{Invalid method type: $method});
72             }
73              
74 35 50 66     147 if ( ref $request ne 'HASH' && ref $request ne 'ARRAY' ) {
75 0         0 return MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request(
76             $request);
77             }
78              
79 35         72 my @methods = ();
80             METHOD:
81 35 100       113 foreach my $obj ( ref $request eq 'HASH' ? $request : @{$request} ) {
  8         18  
82 42 100       112 if ( ref $obj ne 'HASH' ) {
83 4         10 push @methods,
84             MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request;
85 4         8 next METHOD;
86             }
87 38 100       128 my $method = $obj->{method} if exists $obj->{method};
88 38 100 100     251 if (!( defined $method
89             && $method =~ m/^[A-Za-z_\.][A-Za-z0-9_\.]*$/xms
90             )
91             )
92             {
93 4         13 push @methods,
94             MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request(
95             $method);
96 4         11 next METHOD;
97             }
98 34 100       111 my $params = $obj->{params} if exists $obj->{params};
99 34 100 100     174 if ( defined $params
      100        
100             && ref $params ne 'ARRAY'
101             && ref $params ne 'HASH' )
102             {
103 1         5 push @methods,
104             MojoX::JSON::RPC::Dispatcher::Method->new->invalid_params(
105             'NOT array or hash: ' . $params );
106 1         4 next METHOD;
107             }
108              
109             ## Hack to support JSON-RPC 1.1
110 33 50 66     252 if ( !( exists $obj->{id} && defined $obj->{id} )
      66        
      33        
      33        
111             && exists $obj->{version}
112             && defined $obj->{version}
113             && $obj->{version} eq '1.1' )
114             {
115 0         0 $obj->{id} = '#'; # TODO: fix this
116             }
117              
118             ## Create method object
119             my $m = MojoX::JSON::RPC::Dispatcher::Method->new(
120             method => $method,
121             id => exists $obj->{id} ? $obj->{id} : undef
122 33 100       195 );
123 33 100 66     344 if ( !( exists $obj->{id} && defined $obj->{id} ) ) {
124 5         14 $m->is_notification(1);
125             }
126 33 100       91 if ( defined $params ) { # process parameters
127 26 50 66     78 if ( ref $params ne 'ARRAY' && ref $params ne 'HASH' ) {
128 0         0 $m->invalid_params($params);
129             }
130             else {
131 26         71 $m->params($params);
132             }
133             }
134 33         201 push @methods, $m;
135             }
136 35         178 return \@methods;
137             }
138              
139             sub _handle_methods {
140 37     37   73 my ( $self, $methods ) = @_;
141              
142 37         82 my $log = $self->app->log;
143              
144 37 100       280 if ( ref $methods eq 'MojoX::JSON::RPC::Dispatcher::Method' ) {
145 2 50       6 if ( $methods->has_error ) {
146 2         11 return $methods->response;
147             }
148 0         0 $methods = [$methods];
149             }
150 35 100       51 if ( scalar @{$methods} == 0 ) { # empty
  35         100  
151             return
152 1         4 MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request
153             ->response;
154             }
155              
156 34         44 my @responses;
157 34         161 my $service = $self->stash('service');
158 34         431 my $rpcs = $service->{_rpcs};
159             METHOD:
160 34 50       89 foreach my $m ( ref $methods eq 'ARRAY' ? @{$methods} : $methods ) {
  34         73  
161 42 100       186 if ( $m->has_error ) {
162 9         52 push @responses, $m->response;
163 9         152 next METHOD;
164             }
165             my $rpc = $rpcs->{ $m->method }
166 33 100       224 if exists $rpcs->{ $m->method };
167             my $code_ref = $rpc->{method}
168 33 50 66     404 if defined $rpc && exists $rpc->{method};
169 33 100       66 if ( defined $code_ref ) {
170              
171             # deal with params and calling
172             # pass in svc obj and mojo tx if necessary
173 31         43 eval {
174             $m->result(
175             $code_ref->(
176             exists $rpc->{with_svc_obj}
177             && $rpc->{with_svc_obj} ? $service : (),
178             exists $rpc->{with_mojo_tx}
179             && $rpc->{with_mojo_tx} ? $self->tx : (),
180             exists $rpc->{with_self}
181             && $rpc->{with_self} ? $self : (),
182             defined $m->params
183             ? ref $m->params eq 'ARRAY'
184 31 100 66     273 ? @{ $m->params }
  22 100 66     267  
    100 66        
    100          
    100          
185             : $m->params
186             : ()
187             )
188             );
189             };
190 31 100       990 if ($@) {
191 1         6 my $err = $@;
192 1         5 my $handler = $self->stash('exception_handler');
193 1 50       11 if (ref $handler eq 'CODE') {
194 1         5 $handler->($self, $err, $m);
195             }
196             else {
197 0         0 $m->internal_error($@);
198             }
199             }
200             }
201             else {
202 2         5 $m->method_not_found( $m->method );
203             }
204 33 100 66     132 if ( !$m->is_notification || $m->has_error ) {
205 28         518 push @responses, $m->response;
206             }
207             }
208 34 100       475 return scalar @responses > 1 ? \@responses : $responses[0];
209             }
210              
211             # Translate JSON-RPC error code to HTTP status.
212             sub _translate_error_code_to_status {
213 34     34   98 my ( $self, $code ) = @_;
214 34         281 my %trans = (
215             q{} => 200,
216             '-32600' => 400,
217             '-32601' => 404,
218             '-32602' => 200, # wants the user to get the rpc error
219             '-32700' => 400
220             );
221 34   100     175 $code ||= q{};
222 34 50       198 return exists $trans{$code} ? $trans{$code} : 500;
223             }
224              
225             1;
226              
227             __END__