view release on metacpan or search on metacpan
NAME
Dancer::Plugin::RPC::RESTISH - Simple plugin to implement a restish
interface.
SYNOPSIS
In the Controler-bit:
use Dancer::Plugin::RPC::RESTISH;
restish '/endpoint' => {
publish => 'pod',
arguments => ['MyProject::Admin'],
};
and in the Model-bit (MyProject::Admin):
package MyProject::Admin;
=for restish GET@ability/:id rpc_get_ability_details
example/lib/Example.pm view on Meta::CPAN
{
my $system_config = Example::EndpointConfig->new(
publish => 'pod',
bread_board => $system_api,
plugin_arguments => {
arguments => ['Example::API::System'],
},
);
my @plugins = grep { /^RPC::/ } keys %{ config->{plugins} };
for my $plugin (@plugins) {
$system_config->register_endpoint($plugin, '/system');
}
}
{
my $db_config = Example::EndpointConfig->new(
publish => 'config',
bread_board => $db_api,
);
my @plugins = grep { /^RPC::/ } keys %{ config->{plugins} };
for my $plugin (@plugins) {
for my $path (keys %{ config->{plugins}{$plugin} }) {
$db_config->register_endpoint($plugin, $path);
}
}
}
1;
example/lib/Example/EndpointConfig.pm view on Meta::CPAN
service 'Client::MetaCpan' => as (
class => 'Client::MetaCpan',
dependencies => {
base_uri => literal config->{base_uri},
),
};
};
),
);
$config->register_endpoint('RPC::JSONRPC' => '/metacpan');
$config->register_endpoint('RPC::XMLRPC' => '/metacpan');
=head1 ATTRIBUTES
=head2 publish [required]
This attribute can have the value of B<config> or B<pod>, it will be bassed to
L<Dancer::Plugin::RPC>
=head2 callback [optional]
example/lib/Example/EndpointConfig.pm view on Meta::CPAN
return $instance->$code(@arguments);
};
}
sub _registrar_for_plugin {
my $self = shift;
my ($plugin) = @_;
return $_plugin_info{$plugin}{registrar} // die "Cannot find plugin '$plugin'";
}
=head2 endpoint_config($path)
Returns a config-hash for the C<Dancer::Plugin::RPC::*> plugins.
=cut
sub endpoint_config {
my $self = shift;
my ($path) = @_;
return {
publish => $self->publish,
code_wrapper => $self->code_wrapper,
(defined $self->callback
? (callback => $self->callback)
: ()
),
(defined $self->plugin_arguments
? (%{ $self->plugin_arguments })
: ()
),
};
}
=head2 register_endpoint($plugin, $path)
=cut
sub register_endpoint {
my $self = shift;
my ($plugin, $path) = @_;
my $registrar = $self->_registrar_for_plugin($plugin);
$registrar->($path, $self->endpoint_config($path));
}
use namespace::autoclean;
1;
=head1 COPYRIGHT
(c) MMXIX - Abe Timmerman <abeltje@cpan.org>
=cut
lib/Dancer/Plugin/RPC/RESTISH.pm view on Meta::CPAN
# A char between the HTTP-Method and the REST-route
our $_HM_POSTFIX = '@';
my %dispatch_builder_map = (
pod => \&build_dispatcher_from_pod,
config => \&build_dispatcher_from_config,
);
register PLUGIN_NAME ,=> sub {
my ($self, $endpoint, $arguments) = plugin_args(@_);
my $allow_origin = $arguments->{cors_allow_origin} || '';
my @allowed_origins = split(' ', $allow_origin);
my $publisher;
given ($arguments->{publish} // 'config') {
when (exists $dispatch_builder_map{$_}) {
$publisher = $dispatch_builder_map{$_};
$arguments->{arguments} = plugin_setting() if $_ eq 'config';
}
default {
$publisher = $_;
}
}
my $dispatcher = $publisher->($arguments->{arguments}, $endpoint);
my $lister = Dancer::RPCPlugin::DispatchMethodList->new();
$lister->set_partial(
protocol => PLUGIN_NAME,
endpoint => $endpoint,
methods => [ sort keys %{ $dispatcher } ],
);
my $code_wrapper = $arguments->{code_wrapper}
? $arguments->{code_wrapper}
: sub {
my $code = shift;
my $pkg = shift;
$code->(@_);
};
lib/Dancer/Plugin/RPC/RESTISH.pm view on Meta::CPAN
if ($allow_origin && $has_origin && !$allowed_origin) {
debug("[RESTISH-CORS] '$has_origin' not allowed ($allow_origin)");
status(403);
content_type('text/plain');
return "[CORS] $has_origin not allowed";
}
# method_name should exist...
# we need to turn 'GET@some_resource/:id' into a regex that we can use
# to match this request so we know what thing to call...
(my $method_name = $request_path) =~ s{^$endpoint/}{};
my ($found_match, $found_method);
my @sorted_dispatch_keys = sort {
# reverse length of the regex we use to match
my ($am, $ar) = split(/\b$_HM_POSTFIX/, $a);
$ar =~ s{/:\w+}{/[^/]+};
my ($bm, $br) = split(/\b$_HM_POSTFIX/, $b);
$br =~ s{/:\w+}{/[^/]+};
length($br) <=> length($ar)
} keys %$dispatcher;
lib/Dancer/Plugin/RPC/RESTISH.pm view on Meta::CPAN
%$query_args,
};
debug("[handling_restish_request('$request_path' via '$found_match')] ", $method_args);
my Dancer::RPCPlugin::CallbackResult $continue = eval {
(my $match_re = $found_match) =~ s{:\w+}{[^/]+}g;
local $Dancer::RPCPlugin::ROUTE_INFO = {
plugin => PLUGIN_NAME,
route_matched => $found_match,
matched_re => $match_re,
endpoint => $endpoint,
rpc_method => $method_name,
full_path => request->path,
http_method => $http_method,
};
$callback
? $callback->(request(), $method_name, $method_args)
: callback_success();
};
my $error = $@;
my $response;
lib/Dancer/Plugin/RPC/RESTISH.pm view on Meta::CPAN
if (config->{encoding} && config->{encoding} =~ m{^utf-?8$}i) {
$jsonise_options->{utf8} = 1;
}
# non-refs will be send as-is
return ref($response)
? to_json($response, $jsonise_options)
: $response;
};
debug("setting routes (restish): $endpoint ", $lister);
# split the keys in $dispatcher so we can register 'any' methods for all
# the handler will know what to do...
for my $dispatch_route (keys %$dispatcher) {
my ($hm, $route) = split(/$_HM_POSTFIX/, $dispatch_route, 2);
my $dancer_route = "$endpoint/$route";
debug("[restish] registering `any $dancer_route` ($hm)");
any $dancer_route, $handle_call;
}
};
sub build_dispatcher_from_pod {
my ($pkgs, $endpoint) = @_;
debug("[build_dispatcher_from_pod]");
return dispatch_table_from_pod(
plugin => 'restish',
packages => $pkgs,
endpoint => $endpoint,
);
}
sub build_dispatcher_from_config {
my ($config, $endpoint) = @_;
debug("[build_dispatcher_from_config]");
return dispatch_table_from_config(
plugin => 'restish',
config => $config,
endpoint => $endpoint,
);
}
register_plugin();
true;
=head1 NAME
Dancer::Plugin::RPC::RESTISH - Simple plugin to implement a restish interface.
=head1 SYNOPSIS
In the Controler-bit:
use Dancer::Plugin::RPC::RESTISH;
restish '/endpoint' => {
publish => 'pod',
arguments => ['MyProject::Admin'],
cors_allow_origin => '*',
};
and in the Model-bit (B<MyProject::Admin>):
package MyProject::Admin;
=for restish GET@ability/:id rpc_get_ability_details
lib/Dancer/Plugin/RPC/RESTISH.pm view on Meta::CPAN
=for restish DELETE@resource/:id delete_resource /rest
The third argument (the base_path) is optional.
=back
The plugin for RESTISH also adds 2 fields to C<$Dancer::RPCPlugin::ROUTE_INFO>:
local $Dancer::RPCPlugin::ROUTE_INFO = {
plugin => PLUGIN_NAME,
endpoint => $endpoint,
rpc_method => $method_name,
full_path => request->path,
http_method => $http_method,
# These two are added
route_matched => $found_match, # PATCH@resource/:id
matched_re => $match_re, # PATCH@resource/[^/]+
};
=head2 CORS (Cross-Origin Resource Sharing)
t/225-register-restish.t view on Meta::CPAN
use Dancer::RPCPlugin::CallbackResult;
use Dancer::RPCPlugin::DispatchItem;
use Dancer::RPCPlugin::ErrorResponse;
use Dancer::Test;
{
note("default publish == 'config'");
set(plugins => {
'RPC::RESTISH' => {
'/endpoint' => {
'TestProject::SystemCalls' => {
'GET@ping' => 'do_ping',
'GET@version' => 'do_version',
},
},
}
});
set(encoding => 'utf-8');
restish '/endpoint' => { };
route_exists([GET => '/endpoint/ping'], "GET /endpoint/ping registered");
route_exists([GET => '/endpoint/version'], "GET /endpoint/version registered");
my $response = dancer_response(
GET => '/endpoint/ping',
);
my $ping = from_json('{"response": true}');
if (JSON->VERSION >= 2.90) {
my $t = 1;
$ping->{response} = bless \$t, 'JSON::PP::Boolean';
}
is_deeply(
from_json($response->{content}),
$ping,
"GET /endpoint/ping"
) or diag(explain($response));
}
{
note("publish is code that returns the dispatch-table");
restish '/endpoint2' => {
publish => sub {
eval { require TestProject::SystemCalls; };
error("Cannot load: $@") if $@;
return {
'GET@version' => dispatch_item(
code => TestProject::SystemCalls->can('do_version'),
package => 'TestProject::SystemCalls',
),
};
},
callback => sub { return callback_success(); },
};
route_exists([GET => '/endpoint2/version'], "GET /endpoint2/version registered");
my $response = dancer_response(
GET => '/endpoint2/version',
);
is_deeply(
from_json($response->{content}),
{ software_version => $TestProject::SystemCalls::VERSION },
"GET /endpoint2/version"
);
}
{
note("callback fails");
restish '/fail1' => {
publish => sub {
eval { require TestProject::SystemCalls; };
error("Cannot load: $@") if $@;
return {
t/225-register-restish.t view on Meta::CPAN
my $result = $response->header('content-type') eq 'application/json'
? from_json($response->{content})
: $response->{content};
is_deeply(
$result,
{
error_code => -32500,
error_message => "terrible death\n",
error_data => { },
},
"GET /endpoint_fail2/version (callback dies)"
) or diag(explain($result));
}
{
note("callback returns unknown object");
restish '/fail3' => {
publish => sub {
eval { require TestProject::SystemCalls; };
error("Cannot load: $@") if $@;
return {