File Coverage

blib/lib/Dancer/Plugin/RPC/XMLRPC.pm
Criterion Covered Total %
statement 64 105 60.9
branch 4 26 15.3
condition 2 14 14.2
subroutine 16 18 88.8
pod 3 3 100.0
total 89 166 53.6


line stmt bran cond sub pod time code
1             package Dancer::Plugin::RPC::XMLRPC;
2 5     5   543272 use v5.10;
  5         30  
3 5     5   23 use Dancer ':syntax';
  5         7  
  5         28  
4 5     5   1766 use Dancer::Plugin;
  5         2166  
  5         287  
5 5     5   29 use Scalar::Util 'blessed';
  5         9  
  5         216  
6              
7 5     5   721 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  5         29  
  5         37  
8              
9 5     5   777 use Dancer::RPCPlugin::CallbackResult;
  5         12  
  5         225  
10 5     5   611 use Dancer::RPCPlugin::DispatchFromConfig;
  5         10  
  5         197  
11 5     5   549 use Dancer::RPCPlugin::DispatchFromPod;
  5         11  
  5         255  
12 5     5   37 use Dancer::RPCPlugin::DispatchItem;
  5         12  
  5         165  
13 5     5   829 use Dancer::RPCPlugin::DispatchMethodList;
  5         10  
  5         176  
14 5     5   498 use Dancer::RPCPlugin::FlattenData;
  5         29  
  5         171  
15              
16 5     5   1550 use RPC::XML::ParserFactory;
  5         4476  
  5         27  
17              
18             my %dispatch_builder_map = (
19             pod => \&build_dispatcher_from_pod,
20             config => \&build_dispatcher_from_config,
21             );
22              
23             register xmlrpc => sub {
24 14     14   6373 my($self, $endpoint, $arguments) = plugin_args(@_);
25              
26 14         75 my $publisher;
27 14   100     54 given ($arguments->{publish} // 'config') {
28 14         45 when (exists $dispatch_builder_map{$_}) {
29 13         26 $publisher = $dispatch_builder_map{$_};
30 13 100       61 $arguments->{arguments} = plugin_setting() if $_ eq 'config';
31             }
32 1         1 default {
33 1         3 $publisher = $_;
34             }
35             }
36 14         200 my $dispatcher = $publisher->($arguments->{arguments}, $endpoint);
37              
38 14         87 my $lister = Dancer::RPCPlugin::DispatchMethodList->new();
39             $lister->set_partial(
40             protocol => 'xmlrpc',
41             endpoint => $endpoint,
42 14         35 methods => [ sort keys %{ $dispatcher } ],
  14         77  
43             );
44              
45             my $code_wrapper = $arguments->{code_wrapper}
46             ? $arguments->{code_wrapper}
47             : sub {
48 0     0   0 my $code = shift;
49 0         0 my $pkg = shift;
50 0         0 $code->(@_);
51 14 50       107 };
52 14         36 my $callback = $arguments->{callback};
53              
54 14         61 debug("Starting xmlrpc-handler build: ", $lister);
55             my $handle_call = sub {
56 3 50   3   16568 if (request->content_type ne 'text/xml') {
57 0         0 pass();
58             }
59 3         52 debug("[handle_xmlrpc_request] Processing: ", request->body);
60              
61 3         537 local $RPC::XML::ENCODING = $RPC::XML::ENCODING ='UTF-8';
62 3         24 my $p = RPC::XML::ParserFactory->new();
63 3         3132 my $request = $p->parse(request->body);
64 0         0 my $method_name = $request->name;
65 0         0 debug("[handle_xmlrpc_call($method_name)] ", $request->args);
66              
67 0 0       0 if (! exists $dispatcher->{$method_name}) {
68 0         0 warning("$endpoint/#$method_name not found, pass()");
69 0         0 pass();
70             }
71              
72 0         0 content_type 'text/xml';
73 0         0 my $response;
74 0         0 my @method_args = map $_->value, @{$request->args};
  0         0  
75 0         0 my Dancer::RPCPlugin::CallbackResult $continue = eval {
76 0 0       0 $callback
77             ? $callback->(request(), $method_name, @method_args)
78             : callback_success();
79             };
80              
81 0 0       0 if (my $error = $@) {
82 0         0 $response = {
83             faultCode => 500,
84             faultString => $error,
85             };
86 0         0 return xmlrpc_response($response);
87             }
88 0 0 0     0 if (!blessed($continue) || !$continue->isa('Dancer::RPCPlugin::CallbackResult')) {
    0 0        
89 0         0 $response = {
90             faultCode => 500,
91             faultString => "Internal error: 'callback_result' wrong class " . blessed($continue),
92             };
93             }
94             elsif (blessed($continue) && !$continue->success) {
95 0         0 $response = {
96             faultCode => $continue->error_code,
97             faultString => $continue->error_message,
98             };
99             }
100             else {
101 0         0 my Dancer::RPCPlugin::DispatchItem $di = $dispatcher->{$method_name};
102 0         0 my $handler = $di->code;
103 0         0 my $package = $di->package;
104              
105 0         0 $response = eval {
106 0         0 $code_wrapper->($handler, $package, $method_name, @method_args);
107             };
108              
109 0         0 debug("[handling_xmlrpc_response($method_name)] ", $response);
110 0 0       0 if (my $error = $@) {
111 0         0 $response = {
112             faultCode => 500,
113             faultString => $error,
114             };
115             }
116 0 0 0     0 if (blessed($response) && $response->can('as_xmlrpc_fault')) {
    0          
117 0         0 $response = $response->as_xmlrpc_fault;
118             }
119             elsif (blessed($response)) {
120 0         0 $response = flatten_data($response);
121             }
122             }
123 0         0 return xmlrpc_response($response);
124 14         473 };
125              
126 14         72 debug("setting route (xmlrpc): $endpoint ", $lister);
127 14         317 post $endpoint, $handle_call;
128             };
129              
130             sub xmlrpc_response {
131 0     0 1 0 my ($data) = @_;
132              
133 0         0 my $response;
134 0 0 0     0 if (ref $data eq 'HASH' && exists $data->{faultCode}) {
    0          
135 0         0 $response = RPC::XML::response->new(RPC::XML::fault->new(%$data));
136             }
137             elsif (grep /^faultCode$/, grep defined $_, @_) {
138 0         0 $response = RPC::XML::response->new(RPC::XML::fault->new(@_));
139             }
140             else {
141 0         0 $response = RPC::XML::response->new(@_);
142             }
143 0         0 debug("[xmlrpc_response] ", $response->as_string);
144 0         0 return $response->as_string;
145             }
146              
147             sub build_dispatcher_from_pod {
148 6     6 1 17 my ($pkgs, $endpoint) = @_;
149 6         21 debug("[build_dispatcher_from_pod]");
150 6         64 return dispatch_table_from_pod(
151             plugin => 'xmlrpc',
152             packages => $pkgs,
153             endpoint => $endpoint,
154             );
155             }
156              
157             sub build_dispatcher_from_config {
158 7     7 1 17 my ($config, $endpoint) = @_;
159 7         24 debug("[build_dispatcher_from_config] ");
160              
161 7         147 return dispatch_table_from_config(
162             plugin => 'xmlrpc',
163             config => $config,
164             endpoint => $endpoint,
165             );
166             }
167              
168             register_plugin();
169             true;
170              
171             =head1 NAME
172              
173             Dancer::Plugin::RPC::XMLRPC - XMLRPC Plugin for Dancer
174              
175             =head2 SYNOPSIS
176              
177             In the Controler-bit:
178              
179             use Dancer::Plugin::RPC::XMLRPC;
180             xmlrpc '/endpoint' => {
181             publish => 'pod',
182             arguments => ['MyProject::Admin']
183             };
184              
185             and in the Model-bit (B<MyProject::Admin>):
186              
187             package MyProject::Admin;
188            
189             =for xmlrpc rpc.abilities rpc_show_abilities
190            
191             =cut
192            
193             sub rpc_show_abilities {
194             return {
195             # datastructure
196             };
197             }
198             1;
199              
200             =head1 DESCRIPTION
201              
202             This plugin lets one bind an endpoint to a set of modules with the new B<xmlrpc> keyword.
203              
204             =head2 xmlrpc '/endpoint' => \%publisher_arguments;
205              
206             =head3 C<\%publisher_arguments>
207              
208             =over
209              
210             =item callback => $coderef [optional]
211              
212             The callback will be called just before the actual rpc-code is called from the
213             dispatch table. The arguments are positional: (full_request, method_name).
214              
215             my Dancer::RPCPlugin::CallbackResult $continue = $callback
216             ? $callback->(request(), $method_name, @method_args)
217             : callback_success();
218              
219             The callback should return a L<Dancer::RPCPlugin::CallbackResult> instance:
220              
221             =over 8
222              
223             =item * on_success
224              
225             callback_success()
226              
227             =item * on_failure
228              
229             callback_fail(
230             error_code => <numeric_code>,
231             error_message => <error message>
232             )
233              
234             =back
235              
236             =item code_wrapper => $coderef [optional]
237              
238             The codewrapper will be called with these positional arguments:
239              
240             =over 8
241              
242             =item 1. $call_coderef
243              
244             =item 2. $package (where $call_coderef is)
245              
246             =item 3. $method_name
247              
248             =item 4. @arguments
249              
250             =back
251              
252             The default code_wrapper-sub is:
253              
254             sub {
255             my $code = shift;
256             my $pkg = shift;
257             $code->(@_);
258             };
259              
260             =item publisher => <config | pod | \&code_ref>
261              
262             The publiser key determines the way one connects the rpc-method name with the actual code.
263              
264             =over
265              
266             =item publisher => 'config'
267              
268             This way of publishing requires you to create a dispatch-table in the app's config YAML:
269              
270             plugins:
271             "RPC::XMLRPC":
272             '/endpoint':
273             'MyProject::Admin':
274             admin.someFunction: rpc_admin_some_function_name
275             'MyProject::User':
276             user.otherFunction: rpc_user_other_function_name
277              
278             The Config-publisher doesn't use the C<arguments> value of the C<%publisher_arguments> hash.
279              
280             =item publisher => 'pod'
281              
282             This way of publishing enables one to use a special POD directive C<=for xmlrpc>
283             to connect the rpc-method name to the actual code. The directive must be in the
284             same file as where the code resides.
285              
286             =for xmlrpc admin.someFunction rpc_admin_some_function_name
287              
288             The POD-publisher needs the C<arguments> value to be an arrayref with package names in it.
289              
290             =item publisher => \&code_ref
291              
292             This way of publishing requires you to write your own way of building the dispatch-table.
293             The code_ref you supply, gets the C<arguments> value of the C<%publisher_arguments> hash.
294              
295             A dispatch-table looks like:
296              
297             return {
298             'admin.someFuncion' => dispatch_item(
299             package => 'MyProject::Admin',
300             code => MyProject::Admin->can('rpc_admin_some_function_name'),
301             ),
302             'user.otherFunction' => dispatch_item(
303             package => 'MyProject::User',
304             code => MyProject::User->can('rpc_user_other_function_name'),
305             ),
306             }
307              
308             =back
309              
310             =item arguments => <anything>
311              
312             The value of this key depends on the publisher-method chosen.
313              
314             =back
315              
316             =head2 =for xmlrpc xmlrpc-method-name sub-name
317              
318             This special POD-construct is used for coupling the xmlrpc-methodname to the
319             actual sub-name in the current package.
320              
321             =head1 INTERNAL
322              
323             =head2 xmlrpc_response
324              
325             Serializes the data passed as an xmlrpc response.
326              
327             =head2 build_dispatcher_from_config
328              
329             Creates a (partial) dispatch table from data passed from the (YAML)-config file.
330              
331             =head2 build_dispatcher_from_pod
332              
333             Creates a (partial) dispatch table from data provided in POD.
334              
335             =head1 COPYRIGHT
336              
337             (c) MMXV - Abe Timmerman <abeltje@cpan.org>
338              
339             =cut