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   17 use Mojo::Base 'Mojolicious::Controller';
  3         4  
  3         16  
4 3     3   457 use Mojo::JSON qw(decode_json);
  3         6  
  3         143  
5 3     3   1121 use MojoX::JSON::RPC::Dispatcher::Method;
  3         6  
  3         18  
6 3     3   86 use MojoX::JSON::RPC::Service;
  3         4  
  3         15  
7              
8             # process JSON-RPC call
9             sub call {
10 37     37 1 132618 my ($self) = @_;
11              
12 37         85 my $rpc_response = $self->_handle_methods( $self->_acquire_methods );
13 37 100       150 if ( !$rpc_response ) { # is notification
14 3         8 $self->tx->res->code(204);
15 3         29 return $self->rendered;
16             }
17 34         67 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     275 : q{}
23             )
24             );
25 34         192 $res->headers->content_type('application/json-rpc');
26 34         438 return $self->render(json => $rpc_response);
27             }
28              
29             sub _acquire_methods {
30 37     37   43 my ($self) = @_;
31              
32 37         78 my $log = $self->app->log;
33 37         204 my $req = $self->req; # Mojo::Message::Request object
34 37         298 my $method = $req->method; # GET / POST
35 37         151 my $request;
36              
37 37 100       77 if ( $method eq 'POST' ) {
    50          
38 35 50       95 if ( $log->is_level('debug') ) {
39 0         0 $log->debug( 'REQUEST: BODY> ' . $req->body );
40             }
41            
42 35         237 my $decode_error;
43 35 100       39 eval{ $request = decode_json( $req->body ); 1; } or $decode_error = $@;
  35         68  
  33         21426  
44 35 100       2209 if ( $decode_error ) {
45 2         12 $log->debug( 'REQUEST: JSON error> ' . $decode_error );
46 2         148 return MojoX::JSON::RPC::Dispatcher::Method->new->parse_error(
47             $decode_error );
48             }
49             }
50             elsif ( $method eq 'GET' ) {
51 2         11 my $params = $req->query_params->to_hash;
52 2         137 my $decoded_params;
53              
54 2 50       6 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     114 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         52 my @methods = ();
80             METHOD:
81 35 100       112 foreach my $obj ( ref $request eq 'HASH' ? $request : @{$request} ) {
  8         12  
82 42 100       94 if ( ref $obj ne 'HASH' ) {
83 4         9 push @methods,
84             MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request;
85 4         7 next METHOD;
86             }
87 38 100       91 my $method = $obj->{method} if exists $obj->{method};
88 38 100 100     181 if (!( defined $method
89             && $method =~ m/^[A-Za-z_\.][A-Za-z0-9_\.]*$/xms
90             )
91             )
92             {
93 4         12 push @methods,
94             MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request(
95             $method);
96 4         11 next METHOD;
97             }
98 34 100       89 my $params = $obj->{params} if exists $obj->{params};
99 34 100 100     109 if ( defined $params
      100        
100             && ref $params ne 'ARRAY'
101             && ref $params ne 'HASH' )
102             {
103 1         3 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     177 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       145 );
123 33 100 66     344 if ( !( exists $obj->{id} && defined $obj->{id} ) ) {
124 5         9 $m->is_notification(1);
125             }
126 33 100       72 if ( defined $params ) { # process parameters
127 26 50 66     60 if ( ref $params ne 'ARRAY' && ref $params ne 'HASH' ) {
128 0         0 $m->invalid_params($params);
129             }
130             else {
131 26         52 $m->params($params);
132             }
133             }
134 33         165 push @methods, $m;
135             }
136 35         130 return \@methods;
137             }
138              
139             sub _handle_methods {
140 37     37   63 my ( $self, $methods ) = @_;
141              
142 37         71 my $log = $self->app->log;
143              
144 37 100       234 if ( ref $methods eq 'MojoX::JSON::RPC::Dispatcher::Method' ) {
145 2 50       4 if ( $methods->has_error ) {
146 2         11 return $methods->response;
147             }
148 0         0 $methods = [$methods];
149             }
150 35 100       38 if ( scalar @{$methods} == 0 ) { # empty
  35         59  
151             return
152 1         3 MojoX::JSON::RPC::Dispatcher::Method->new->invalid_request
153             ->response;
154             }
155              
156 34         39 my @responses;
157 34         80 my $service = $self->stash('service');
158 34         290 my $rpcs = $service->{_rpcs};
159             METHOD:
160 34 50       69 foreach my $m ( ref $methods eq 'ARRAY' ? @{$methods} : $methods ) {
  34         56  
161 42 100       122 if ( $m->has_error ) {
162 9         41 push @responses, $m->response;
163 9         107 next METHOD;
164             }
165             my $rpc = $rpcs->{ $m->method }
166 33 100       171 if exists $rpcs->{ $m->method };
167             my $code_ref = $rpc->{method}
168 33 50 66     273 if defined $rpc && exists $rpc->{method};
169 33 100       50 if ( defined $code_ref ) {
170              
171             # deal with params and calling
172             # pass in svc obj and mojo tx if necessary
173 31         42 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     188 ? @{ $m->params }
  22 100 66     156  
    100 66        
    100          
    100          
185             : $m->params
186             : ()
187             )
188             );
189             };
190 31 100       813 if ($@) {
191 1         5 my $err = $@;
192 1         3 my $handler = $self->stash('exception_handler');
193 1 50       11 if (ref $handler eq 'CODE') {
194 1         3 $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     73 if ( !$m->is_notification || $m->has_error ) {
205 28         188 push @responses, $m->response;
206             }
207             }
208 34 100       275 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   56 my ( $self, $code ) = @_;
214 34         140 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     95 $code ||= q{};
222 34 50       147 return exists $trans{$code} ? $trans{$code} : 500;
223             }
224              
225             1;
226              
227             __END__