Dancer2-Plugin-RPC

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

 -     (Dancer2::Plugin::RPC::RESTISH).

2.01 2022-07-11T09:55:40+02:00 (43848fb => Abe Timmerman)
 - (Abe Timmerman, Mon, 11 Jul 2022 09:55:40 +0200) Autocommit for
   distribution Dancer2::Plugin::RPC 2.01 (minor)

 - (Abe Timmerman, Mon, 26 Sep 2022 16:03:34 +0200) Fix documentation

 - (Abe Timmerman, Mon, 26 Sep 2022 16:03:42 +0200) Fix validation
   templates
 -     - Also allow '-' (dash) in endpoints
 -     - Add 'plugin_args' to the 'plugin_config' dict to have an optional
 -	 HashRef to pass to other Dancer2::Plugin::RPC plugins (for now
 -	 Dancer2::Plugin::RPC::RESTISH)

1.99_06 2022-07-01T13:56:39+02:00 (d60d234 => Abe Timmerman)
 - (Abe Timmerman, Fri, 1 Jul 2022 13:56:39 +0200) Autocommit for
   distribution Dancer2::Plugin::RPC 1.99_06 (same)

 - (Abe Timmerman, Mon, 11 Jul 2022 09:51:05 +0200) I screwed up the
   versioning

Changes  view on Meta::CPAN

   versions (Dancer1.3 and Dancer2)

 - (abeltje, Thu, 10 Aug 2017 12:00:17 +0200) More bookkeeping...

 - (abeltje, Wed, 16 Aug 2017 20:04:46 +0200) Fix regex for argument
   validation

 - (abeltje, Wed, 6 Sep 2017 18:54:12 +0200) Documentation fixes, complete
   RESTRPC stuff

 - (abeltje, Wed, 6 Sep 2017 20:12:50 +0200) Introduce explicit endpoints
   for POD-publishing
 -     - The Dancer2::RPCPlugin::DispatchFromPod class has a new attribute
 -	 'endpoint', this will be used with the new optional item for
   endpoint
 -	 in the POD directive
 -	- Renamed the 'label' attribute to plugin (sync with the Dancer
 -	  version) in the Dancer2::RPCPlugin::DispatchFrom* classes
 -     - Had to rename the 'plugin' attribute to 'plugin_object' in those
 -	 classes

 - (abeltje, Wed, 6 Sep 2017 20:21:54 +0200) Fix agent-identifier in
   http-client.

 - (abeltje, Wed, 6 Sep 2017 21:50:01 +0200) Use Params::ValidationCompiler

README.md  view on Meta::CPAN

Dancer2::Plugin::RPC - Namespace for XMLRPC, JSONRPC2 and RESTRPC plugins

# DESCRIPTION

This module contains plugins for [Dancer2](https://metacpan.org/pod/Dancer2): [Dancer2::Plugin::RPC::XMLRPC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ARPC%3A%3AXMLRPC),
[Dancer2::Plugin::RPC::JSONRPC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ARPC%3A%3AJSONRPC) and [Dancer2::Plugin::RPC::RESTRPC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ARPC%3A%3ARESTRPC).

## Dancer2::Plugin::RPC::XMLRPC

This plugin exposes the new keyword `xmlrpc` that is followed by 2 arguments:
the endpoint and the arguments to configure the xmlrpc-calls at this endpoint.

## Dancer2::Plugin::RPC::JSONRPC

This plugin exposes the new keyword `jsonrpc` that is followed by 2 arguments:
the endpoint and the arguments to configure the jsonrpc-calls at this endpoint.

## Dancer2::Plugin::RPC::RESTRPC

This plugin exposes the new keyword `restrpc` that is followed by 2 arguments:
the endpoint and the arguments to configure the restrpc-calls at this endpoint.

## General arguments to xmlrpc/jsonrpc/restrpc

The dispatch table is build by endpoint.

### publish => <config|pod|$coderef>

- publish => **config**

    The dispatch table is build from the YAML-config:
```yaml
        plugins:
            'RPC::XMLRPC':
                '/endpoint1':
                    'Module::Name1':
                        method1: sub1
                        method2: sub2
                    'Module::Name2':
                        method3: sub3
                '/endpoint2':
                    'Module::Name3':
                        method4: sub4
```
    The **arguments** argument should be empty for this publishing type.

- publish => **pod**

    The dispatch table is build by parsing the POD for `=for xmlrpc`,
    `=for jsonrpc` or `=for restrpc`.
```perl
        =for xmlrpc <method_name> <sub_name> [<endpoint>]
```
    The **arguments** argument must be an Arrayref with module names. The
    POD-directive must be in the same file as the code!

- publish => **$coderef**

    With this publishing type, you will need to build your own dispatch table and return it.
```perl
        use Dancer2::RPCPlugin::DispatchItem;
        return {

ex/MixedEndpoints.pm  view on Meta::CPAN

package MixedEndpoints;
use warnings;
use strict;

=head1 DESCRIPTION

Package with calls for diffent endpoints.

=head2 call_for_system

=for xmlrpc system.call call_for_system /system

=for jsonrpc system_call call_for_system /system

=for restrpc call call_for_system /system

=cut

sub call_for_system { return {endpoint => '/system'} }

=head2 call_for_testing

=for xmlrpc testing.call call_for_testing /testing

=for jsonrpc testing_call call_for_testing /testing

=for restrpc call call_for_testing /testing

=cut

sub call_for_testing { return {endpoint => '/testing'} }


=head2 call_for_all_endpoints

=for xmlrpc any.call call_for_all_endpoints

=for jsonrpc any_call call_for_all_endpoints

=for restrpc any-call call_for_all_endpoints

=cut

sub call_for_all_endpoints { return {endpoint => '*'} }

1;

ex/MyApp.pm  view on Meta::CPAN

use Dancer2::RPCPlugin::CallbackResult;

BEGIN {
    set(log => 'debug');
}

my $callback = sub {
    return callback_succes();
};

jsonrpc '/endpoint' => {
    publish   => 'pod',
    arguments => [qw/ MyAppCode /],
    callback  => $callback,
};

restrpc '/rest' => {
    publish   => 'pod',
    arguments => [qw/ MyAppCode /],
    callback  => $callback,
};

xmlrpc '/endpoint' => {
    publish   => 'pod',
    arguments => [qw/ MyAppCode /],
    callback  => $callback,
};

1;

example/bin/do-rpc  view on Meta::CPAN

            $option{call} //= '';

            $class->$new(%option, arguments => \%arguments);
        };

        sub run {
            my $self = shift;
            my ($client, @method);
            given ($self->type) {
                when ('jsonrpc') {
                    $client = JSONRPCClient->new(endpoint => $self->url);
                }
                when ('xmlrpc')  {
                    $client = XMLRPCClient->new(endpoint => $self->url);
                }
                when ('restrpc') {
                    $client = RESTRPCClient->new(endpoint => $self->url);
                    @method = uc($self->method);
                }
            };

            my $answer = $client->call($self->call, @method, $self->arguments);
            if ($self->asjson) {
                my $jsonise_options = {
                    utf8         => 1,
                    allow_nonref => 1,
                    pretty       => 1,

example/bin/do-rpc  view on Meta::CPAN

    package HTTPClient {
        use Moo::Role;
        use URI;
        use HTTP::Tiny;
        use Scalar::Util 'blessed';

        with 'MooseX::Log::Log4perl::Easy';

        our $VERSION = '0.90';

        has endpoint => (is => 'ro', isa => sub { blessed($_[0]) eq 'URI' });
        has client   => (is => 'lazy', isa => sub { blessed($_[0]) eq 'HTTP::Tiny' });
        has ssl_opts => (is => 'ro', default => undef);
        has timeout  => (is => 'ro', default => 300);

        requires 'call';

        around BUILDARGS => sub {
            my $method = shift;
            my $class  = shift;
            my %args = @_;
            if (ref($args{endpoint}) ne 'URI') {
                $args{endpoint} = URI->new($args{endpoint});
            }
            $class->$method(%args);
        };

        sub _build_client {
            my $self = shift;
            return HTTP::Tiny->new(
                agent      => "Dancer2-RPCPlugin-do-rpc/$VERSION",
                verify_SSL => 0,
                timeout    => $self->timeout,

example/bin/do-rpc  view on Meta::CPAN

            my ($method_name, $data) = @_;

            my $request = $self->jsonrpc_request($method_name => $data);
            my $headers = {
                'Content-Type'   => 'application/json',
                'Content-Length' => length($request),
            };
            $self->log_debug(Dumper($headers));
            $self->log_debug($request);
            my $response = $self->client->request(
                POST => $self->endpoint,
                {
                    headers => $headers,
                    content => $request,
                }
            );
            local $Data::Dumper::Indent = 1;
            $self->log_trace("jsonrpc($method_name)". Dumper($response));
            my $result;
            if ($response->{success}) {
                my $p_response = decode_json($response->{content});

example/bin/do-rpc  view on Meta::CPAN

            return RPC::XML::ParserFactory->new();
        }

        sub call {
            my $self = shift;
            my ($method_name, $data) = @_;

            my $request = RPC::XML::request->new($method_name => $data)->as_string();
            $self->log_debug($request);
            my $response = $self->client->request(
                POST => $self->endpoint,
                {
                    headers => {
                        'Content-Type'   => 'text/xml',
                        'Content-Length' => length($request),
                    },
                    content => $request,
                }
            );
            $self->log_trace(Dumper($response));

example/bin/do-rpc  view on Meta::CPAN

        with 'HTTPClient';
        use JSON;
        use URI;
        use Data::Dumper;

        sub call {
            my $self = shift;
            my $call = shift;
            my $http_method = shift || 'GET';

            (my $endpoint = $self->endpoint->as_string) =~ s{/+$}{};
            $endpoint .= "/$call" if $call;

            my $request = @_ ? encode_json(shift) : '';
            $self->log_debug("$http_method: $endpoint => $request");

            my $response = $self->client->request(
                $http_method => $endpoint,
                {
                    headers => {
                        'Content-Type'   => 'application/json',
                        'Content-Length' => length($request),
                    },
                    content => $request,
                }
            );
            local $Data::Dumper::Indent = 1;
            $self->log_trace(Dumper($response));

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'],
        },
    );
    for my $plugin (qw{ RPC::JSONRPC RPC::RESTRPC RPC::XMLRPC}) {
        $system_config->register_endpoint($plugin, '/system');
    }
}
{
    my $example_config = Example::EndpointConfig->new(
        publish     => 'config',
        bread_board => $example_api,
    );
    my $plugins = config->{plugins};
    for my $plugin (keys %$plugins) {
        for my $path (keys %{$plugins->{$plugin}}) {
            $example_config->register_endpoint($plugin, $path);
        }
    }
}

setup_default_route();
1;

=head1 NAME

Example - An example RPC-application for L<Dancer2::Plugin::RPC>

example/lib/Example/API/System.pm  view on Meta::CPAN

    {software_version => 'X.YZ'}

=head2 rpc_list_methods()

=for jsonrpc list_methods rpc_list_methods /system

=for restrpc list_methods rpc_list_methods /system

=for xmlrpc list_methods rpc_list_methods  /system

Returns a struct for all protocols with all endpoints and functions for that endpoint.

=head1 COPYRIGHT

(c) MMXVII - Abe Timmerman <abeltje@cpan.org>

=cut

example/lib/Example/Client/MetaCpan.pm  view on Meta::CPAN


with 'Client::HTTP';

sub call {
    my $self = shift;
    my ($query) = @_;

    $query =~ s{::}{-}g;
    my $params = $self->client->www_form_urlencode({q => $query});

    (my $endpoint = $self->base_uri->as_string) =~ s{/+$}{};
    my $response = $self->client->get("$endpoint/?$params");

    my $content = eval { decode_json($response->{content}) };
    return $content if !$@;
    return { error => $@, data => $response->{content} };
}

use namespace::autoclean;
1;

=head1 COPYRIGHT

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/Dancer2/Plugin/RPC.pm  view on Meta::CPAN

Dancer2::Plugin::RPC - Namespace for XMLRPC, JSONRPC2 and RESTRPC plugins

=head1 DESCRIPTION

This module contains plugins for L<Dancer2>: L<Dancer2::Plugin::RPC::XMLRPC>,
L<Dancer2::Plugin::RPC::JSONRPC> and L<Dancer2::Plugin::RPC::RESTRPC>.

=head2 Dancer2::Plugin::RPC::XMLRPC

This plugin exposes the new keyword C<xmlrpc> that is followed by 2 arguments:
the endpoint and the arguments to configure the xmlrpc-calls at this endpoint.

=head2 Dancer2::Plugin::RPC::JSONRPC

This plugin exposes the new keyword C<jsonrpc> that is followed by 2 arguments:
the endpoint and the arguments to configure the jsonrpc-calls at this endpoint.

=head2 Dancer2::Plugin::RPC::RESTRPC

This plugin exposes the new keyword C<restrpc> that is followed by 2 arguments:
the endpoint and the arguments to configure the restrpc-calls at this endpoint.

=head2 General arguments to xmlrpc/jsonrpc/restrpc

The dispatch table is build by endpoint.

=head3 publish => <config|pod|$coderef>

=over

=item publish => B<config>

The dispatch table is build from the YAML-config:

    plugins:
        'RPC::XMLRPC':
            '/endpoint1':
                'Module::Name1':
                    method1: sub1
                    method2: sub2
                'Module::Name2':
                    method3: sub3
            '/endpoint2':
                'Module::Name3':
                    method4: sub4

The B<arguments> argument should be empty for this publishing type.

=item publish => B<pod>

The dispatch table is build by parsing the POD for C<=for xmlrpc>,
C<=for jsonrpc> or C<=for restrpc>.

lib/Dancer2/Plugin/RPC/JSONRPC.pm  view on Meta::CPAN

use Dancer2::RPCPlugin::ErrorResponse;
use Dancer2::RPCPlugin::FlattenData;

use JSON;
use Scalar::Util 'blessed';
use Time::HiRes 'time';

plugin_keywords PLUGIN_NAME;

sub jsonrpc {
    my ($plugin, $endpoint, $config) = @_;

    my $dispatcher = $plugin->dispatch_builder(
        $endpoint,
        $config->{publish},
        $config->{arguments},
        plugin_setting(),
    )->();

    my $lister = $plugin->partial_method_lister(
        protocol => __PACKAGE__->rpcplugin_tag,
        endpoint => $endpoint,
        methods  => [ sort keys %{ $dispatcher } ],
    );

    my $code_wrapper = $plugin->code_wrapper($config);
    my $callback = $config->{callback};

    $plugin->app->log(debug => "Starting jsonrpc-handler build: ", $lister);
    my $jsonrpc_handler = sub {
        my ($dsl) = @_;

lib/Dancer2/Plugin/RPC/JSONRPC.pm  view on Meta::CPAN

        $dsl->app->log(
            debug => "[handle_jsonrpc_request] Processing: ", $http_request->body
        );

        $dsl->app->response->content_type('application/json');
        my @responses;
        for my $request (@requests) {
            my $method_name = $request->{method};

            if (!exists $dispatcher->{$method_name}) {
                $dsl->app->log(warning => "$endpoint/#$method_name not found.");

#                # single request; might be another handler
#                $dsl->app->pass() if @requests == 1;

                push @responses, jsonrpc_response(
                    $request->{id},
                    error => {
                        code    => -32601,
                        message => "Method '$method_name' not found at '$endpoint' (skipped)",
                    },
                );
                next;
            }

            my $method_args = $request->{params};
            my $start_request = time();
            $dsl->app->log(debug => "[handle_jsonrpc_call($method_name)] ", $method_args);
            my $continue = eval {
                local $Dancer2::RPCPlugin::ROUTE_INFO = {
                    plugin        => PLUGIN_NAME,
                    endpoint      => $endpoint,
                    rpc_method    => $method_name,
                    full_path     => $http_request->path,
                    http_method   => uc($http_request->method),
                };
                $callback
                    ? $callback->($plugin->app->request(), $method_name, $method_args)
                    : callback_success();
            };

            if (my $error = $@) {

lib/Dancer2/Plugin/RPC/JSONRPC.pm  view on Meta::CPAN

        else {
            $response = to_json([grep {defined($_->{id})} @responses], $jsonise_options);
        }

        $dsl->app->log(debug => "[jsonrpc_response] ", $response);
        return $response;
    };

    $plugin->app->add_route(
        method => 'post',
        regexp => $endpoint,
        code   => $jsonrpc_handler,
    );
}

sub unjson {
    my ($body) = @_;
    return if !$body;

    my @requests;
    my $unjson = decode_json($body);

lib/Dancer2/Plugin/RPC/JSONRPC.pm  view on Meta::CPAN


=head1 NAME

Dancer2::Plugin::RPC::JSON - Dancer Plugin to register jsonrpc2 methods.

=head1 SYNOPSIS

In the Controler-bit:

    use Dancer2::Plugin::RPC::JSON;
    jsonrpc '/endpoint' => {
        publish   => 'pod',
        arguments => ['MyProject::Admin']
    };

and in the Model-bit (B<MyProject::Admin>):

    package MyProject::Admin;
    
    =for jsonrpc rpc.abilities rpc_show_abilities
    

lib/Dancer2/Plugin/RPC/JSONRPC.pm  view on Meta::CPAN

    sub rpc_show_abilities {
        return {
            # datastructure
        };
    }
    1;


=head1 DESCRIPTION

This plugin lets one bind an endpoint to a set of modules with the new B<jsonrpc> keyword.

=head2 jsonrpc '/endpoint' => \%publisher_arguments;

=head3 C<\%publisher_arguments>

=over

=item callback => $coderef [optional]

The callback will be called just before the actual rpc-code is called from the
dispatch table. The arguments are positional: (full_request, method_name).

lib/Dancer2/Plugin/RPC/JSONRPC.pm  view on Meta::CPAN

The publiser key determines the way one connects the rpc-method name with the actual code.

=over

=item publisher => 'config'

This way of publishing requires you to create a dispatch-table in the app's config YAML:

    plugins:
        "RPC::JSONRPC":
            '/endpoint':
                'MyProject::Admin':
                    admin.someFunction: rpc_admin_some_function_name
                'MyProject::User':
                    user.otherFunction: rpc_user_other_function_name

The Config-publisher doesn't use the C<arguments> value of the C<%publisher_arguments> hash.

=item publisher => 'pod'

This way of publishing enables one to use a special POD directive C<=for jsonrpc>

lib/Dancer2/Plugin/RPC/RESTRPC.pm  view on Meta::CPAN


    my $dispatcher = $plugin->dispatch_builder(
        $base_url,
        $config->{publish},
        $config->{arguments},
        plugin_setting(),
    )->();

    my $lister = $plugin->partial_method_lister(
        protocol => __PACKAGE__->rpcplugin_tag,
        endpoint => $base_url,
        methods  => [ sort keys %{ $dispatcher } ],
    );

    my $code_wrapper = $plugin->code_wrapper($config);
    my $callback = $config->{callback};

    $plugin->app->log(debug => "Starting handler build: ", $lister);
    my $restrpc_handler = sub {
        my $dsl = shift;

lib/Dancer2/Plugin/RPC/RESTRPC.pm  view on Meta::CPAN

        $dsl->response->content_type('application/json');
        my $response;
        my $method_args = $http_request->body
            ? from_json($http_request->body)
            : undef;
        $dsl->app->log(debug => "[handle_restrpc_call($method_name)] ", $method_args);
        my $start_request = time();
        my Dancer2::RPCPlugin::CallbackResult $continue = eval {
            local $Dancer2::RPCPlugin::ROUTE_INFO = {
                plugin        => PLUGIN_NAME,
                endpoint      => $base_url,
                rpc_method    => $method_name,
                full_path     => $http_request->path,
                http_method   => uc($http_request->method),
            };
            $callback
                ? $callback->($http_request, $method_name, $method_args)
                : callback_success();
        };

        if (my $error = $@) {

lib/Dancer2/Plugin/RPC/RESTRPC.pm  view on Meta::CPAN

            }
            elsif (blessed($response)) {
                $response = flatten_data($response);
            }
        }

        return restrpc_response($dsl, $response);
    };

    for my $call (keys %{ $dispatcher }) {
        my $endpoint = "$base_url/$call";
        $plugin->app->log(debug => "setting route (restrpc): $endpoint ", $lister);
        $plugin->app->add_route(
            method => 'post',
            regexp => $endpoint,
            code   => $restrpc_handler,
        );
    }
}

sub restrpc_response {
    my ($dsl, $data) = @_;

    my $jsonise_options = {canonical => 1};
    if ($dsl->config->{encoding} && $dsl->config->{encoding} =~ m{^utf-?8$}i) {

lib/Dancer2/Plugin/RPC/RESTRPC.pm  view on Meta::CPAN

    sub rpc_show_abilities {
        return {
            # datastructure
        };
    }
    1;

=head1 DESCRIPTION

RESTRPC is a simple protocol that uses HTTP-POST to post a JSON-string (with
C<Content-Type: application/json> to an endpoint. This endpoint is the
C<base_url> concatenated with the rpc-method name.

This plugin lets one bind a base_url to a set of modules with the new B<restrpc> keyword.

=head2 restrpc '/base_url' => \%publisher_arguments;

=head3 C<\%publisher_arguments>

=over

lib/Dancer2/Plugin/RPC/XMLRPC.pm  view on Meta::CPAN

use Dancer2::RPCPlugin::FlattenData;

use RPC::XML;
use RPC::XML::ParserFactory;
use Scalar::Util 'blessed';
use Time::HiRes 'time';

plugin_keywords PLUGIN_NAME;

sub xmlrpc {
    my ($plugin, $endpoint, $config) = @_;

    my $dispatcher = $plugin->dispatch_builder(
        $endpoint,
        $config->{publish},
        $config->{arguments},
        plugin_setting(),
    )->();

    my $lister = $plugin->partial_method_lister(
        protocol => __PACKAGE__->rpcplugin_tag,
        endpoint => $endpoint,
        methods  => [ sort keys %{ $dispatcher } ],
    );

    my $code_wrapper = $plugin->code_wrapper($config);
    my $callback = $config->{callback};

    $plugin->app->log(debug => "Starting xmlrpc-handler build: ", $lister);
    my $xmlrpc_handler = sub {
        my $dsl = shift;

lib/Dancer2/Plugin/RPC/XMLRPC.pm  view on Meta::CPAN

        $dsl->app->log(
            debug => "[handle_xmlrpc_request] Processing: ", $http_request->body
        );

        local $RPC::XML::ENCODING = $RPC::XML::ENCODING ='UTF-8';
        my $p = RPC::XML::ParserFactory->new();
        my $request = $p->parse($http_request->body);
        my $method_name = $request->name;

        if (! exists $dispatcher->{$method_name}) {
            $dsl->app->log(warning => "$endpoint/#$method_name not found, pass()");
            $dsl->pass();
        }

        $dsl->response->content_type('text/xml');
        my $response;
        $dsl->app->log(debug => "[handle_xmlrpc_call($method_name)] ", $request->args);
        my @method_args = map $_->value, @{$request->args};
        my $start_request = time();
        my Dancer2::RPCPlugin::CallbackResult $continue = eval {
            local $Dancer2::RPCPlugin::ROUTE_INFO = {
                plugin        => PLUGIN_NAME,
                endpoint      => $endpoint,
                rpc_method    => $method_name,
                full_path     => $http_request->path,
                http_method   => uc($http_request->method),
            };
            $callback
                ? $callback->($dsl->app->request, $method_name, @method_args)
                : callback_success();
        };

        if (my $error = $@) {

lib/Dancer2/Plugin/RPC/XMLRPC.pm  view on Meta::CPAN

            if (blessed($response) && $response->can('as_xmlrpc_fault')) {
                $response = $response->as_xmlrpc_fault;
            }
            elsif (blessed($response)) {
                $response = flatten_data($response);
            }
        }
        return xmlrpc_response($dsl, $response);
    };

    $plugin->app->log(debug => "setting route (xmlrpc): $endpoint ", $lister);
    $plugin->app->add_route(
        method => 'post',
        regexp => $endpoint,
        code   => $xmlrpc_handler,
    );
}

sub xmlrpc_response {
    my $dsl = shift;
    my ($data) = @_;

    local $RPC::XML::ENCODING = 'UTF-8';
    my $response;

lib/Dancer2/Plugin/RPC/XMLRPC.pm  view on Meta::CPAN


=head1 NAME

Dancer2::Plugin::RPC::XML - XMLRPC Plugin for Dancer2

=head2 SYNOPSIS

In the Controler-bit:

    use Dancer2::Plugin::RPC::XMLRPC;
    xmlrpc '/endpoint' => {
        publish   => 'pod',
        arguments => ['MyProject::Admin']
    };

and in the Model-bit (B<MyProject::Admin>):

    package MyProject::Admin;
    
    =for xmlrpc rpc.abilities rpc_show_abilities
    

lib/Dancer2/Plugin/RPC/XMLRPC.pm  view on Meta::CPAN

    
    sub rpc_show_abilities {
        return {
            # datastructure
        };
    }
    1;

=head1 DESCRIPTION

This plugin lets one bind an endpoint to a set of modules with the new B<xmlrpc> keyword.

=head2 xmlrpc '/endpoint' => \%publisher_arguments;

=head3 C<\%publisher_arguments>

=over

=item callback => $coderef [optional]

The callback will be called just before the actual rpc-code is called from the
dispatch table. The arguments are positional: (full_request, method_name).

lib/Dancer2/Plugin/RPC/XMLRPC.pm  view on Meta::CPAN

The publiser key determines the way one connects the rpc-method name with the actual code.

=over

=item publisher => 'config'

This way of publishing requires you to create a dispatch-table in the app's config YAML:

    plugins:
        "RPC::XMLRPC":
            '/endpoint':
                'MyProject::Admin':
                    admin.someFunction: rpc_admin_some_function_name
                'MyProject::User':
                    user.otherFunction: rpc_user_other_function_name

The Config-publisher doesn't use the C<arguments> value of the C<%publisher_arguments> hash.

=item publisher => 'pod'

This way of publishing enables one to use a special POD directive C<=for xmlrpc>

lib/Dancer2/RPCPlugin.pm  view on Meta::CPAN

sub rpcplugin_tag {
    my $full_name = ref($_[0]) ? ref($_[0]) : $_[0];
    (my $proto = $full_name) =~ s{.*::}{};
    return "\L${proto}";
}

sub dispatch_builder {
    my $self = shift;
    $self->validate_positional_parameters(
        [
            $self->parameter(endpoint  => $self->Required, {store => \my $endpoint}),
            $self->parameter(publish   => $self->Required, {store => \my $publish}),
            $self->parameter(arguments => $self->Optional, {store => \my $arguments}),
            $self->parameter(settings  => $self->Optional, {store => \my $settings}),
        ],
        \@_
    );

    $publish //= 'config';
    if ($publish eq 'config') {
        return sub {
            $self->app->log(
                debug => "[build_dispatch_table_from_config]"
            );
            my $dispatch_builder = Dancer2::RPCPlugin::DispatchFromConfig->new(
                plugin_object => $self,
                plugin        => $self->rpcplugin_tag,
                config        => $settings,
                endpoint      => $endpoint,
            );
            return $dispatch_builder->build_dispatch_table();
        };
    }
    elsif ($publish eq 'pod') {
        return sub {
            $self->app->log(
                debug => "[build_dispatch_table_from_pod]"
            );
            my $dispatch_builder = Dancer2::RPCPlugin::DispatchFromPod->new(
                plugin_object => $self,
                plugin        => $self->rpcplugin_tag,
                packages      => $arguments,
                endpoint      => $endpoint,
            );
            return $dispatch_builder->build_dispatch_table();
        };
    }

    return $publish;
}

sub partial_method_lister {
    my $self = shift;
    $self->validate_parameters(
        {
            $self->parameter(protocol => $self->Required, {store => \my $protocol}),
            $self->parameter(endpoint => $self->Required, {store => \my $endpoint}),
            $self->parameter(methods  => $self->Required, {store => \my $methods}),
        },
        { @_ }
    );

    my $lister = Dancer2::RPCPlugin::DispatchMethodList->new();
    $lister->set_partial(
        protocol => $protocol,
        endpoint => $endpoint,
        methods  => $methods,
    );
    return $lister;
}

sub code_wrapper {
    my $self = shift;
    $self->validate_positional_parameters(
        [ $self->parameter(config => $self->Required, {store => \my $config}) ],
        \@_

lib/Dancer2/RPCPlugin.pm  view on Meta::CPAN

=head1 DESCRIPTION

=head2 dispatch_builder(%parameters)

=head3 Parameters

Positional:

=over

=item 1. endpoint

=item 2. publish

=item 3. arguments (list of packages for POD-publishing)

=item 4. settings (config->{plugins}{RPC::proto})

=back

=head2 rpcplugin_tag

lib/Dancer2/RPCPlugin.pm  view on Meta::CPAN

Setup the structure for listing the rpc-methods that should be in the dispatch-table.

=head3 Arguments

Named:

=over

=item protocol => $plugin-name

=item endpoint => $endpoint

=item methods => $list_of_methodnames

=back

=head2 code_wrapper

Returns a CodeRef that will be used in the execution of the remote procedure call.

=head3 Arguments

lib/Dancer2/RPCPlugin/DefaultRoute.pm  view on Meta::CPAN


Dancer2::RPCPlugin::DefaultRoute - Catch bad-requests and send error-response

=head1 SYNOPSIS

    use Dancer2::RPCPlugin::DefaultRoute;
    setup_default_route();

=head1 DESCRIPTION

Implements default endpoint to generate -32601 'method not found'
or 'path not found' error_response for non existing endpoints

=head2 setup_default_route

Installs a Dancer route-handler for C<< any qr{.+} >> which tries to return an
appropriate error response to the requestor.

=head3 Responses

All responeses will have B<status: 200 OK>

lib/Dancer2/RPCPlugin/DispatchFromConfig.pm  view on Meta::CPAN

has plugin => (
    is       => 'ro',
    isa      => sub { $_[0] =~ qr/^(?:jsonrpc|restrpc|xmlrpc)$/ },
    required => 1,
);
has config => (
    is       => 'ro',
    isa      => sub { ref($_[0]) eq 'HASH' },
    required => 1,
);
has endpoint => (
    is       => 'ro',
    isa      => sub { $_[0] && !ref($_[0]) },
    required => 1,
);

sub build_dispatch_table {
    my $self = shift;
    my $app = $self->plugin_object->app;
    my $config = $self->config->{ $self->endpoint };

    my @packages = keys %$config;

    my $dispatch;
    for my $package (@packages) {
        eval "require $package";
        if (my $error = $@) {
            $app->log(error => "Cannot load '$package': $error");
            die "Cannot load $package ($error) in build_dispatch_table_from_config\n";
        }

        my @rpc_methods = keys %{ $config->{$package} };
        for my $rpc_method (@rpc_methods) {
            my $subname = $config->{$package}{$rpc_method};
            $app->log(
                debug => "[bdfc] @{[$self->endpoint]}: $rpc_method => $subname"
            );
            if (my $handler = $package->can($subname)) {
                $dispatch->{$rpc_method} = Dancer2::RPCPlugin::DispatchItem->new(
                    package => $package,
                    code    => $handler
                );
            }
            else {
                die "Handler not found for $rpc_method: $package\::$subname doesn't seem to exist.\n";
            }

lib/Dancer2/RPCPlugin/DispatchFromConfig.pm  view on Meta::CPAN

Named, list:

=over

=item plugin_object => $plugin

=item plugin => <xmlrpc|jsonrpc|jsonrpc>

=item config => $config_from_plugin

=item endpoint => $endpoint

=back

=head3 Responses

An instantiated object.

=head2 $dtb->build_dispatch_table()

=head3 Parameters

lib/Dancer2/RPCPlugin/DispatchFromPod.pm  view on Meta::CPAN

has plugin => (
    is       => 'ro',
    isa      => sub { $_[0] =~ qr/^(?:jsonrpc|restrpc|xmlrpc)$/ },
    required => 1,
);
has packages => (
    is       => 'ro',
    isa      => sub { ref($_[0]) eq 'ARRAY' },
    required => 1,
);
has endpoint => (
    is       => 'ro',
    isa      => sub { $_[0] && !ref($_[0]) },
    required => 1,
);

sub build_dispatch_table {
    my $self = shift;
    my $app = $self->plugin_object->app;

    my $pp = Pod::Simple::PullParser->new();

lib/Dancer2/RPCPlugin/DispatchFromPod.pm  view on Meta::CPAN

    while (my $token = $p->get_token) {
        next if not ($token->is_start && $token->is_tag('for'));

        my $label = $token->attr('target');

        my $ntoken = $p->get_token;
        while (!$ntoken->can('text')) { $ntoken = $p->get_token; }

        $app->log(debug => "=for-token $label => ", $ntoken->text);
        my ($if_name, $code_name, $ep_name) = split " ", $ntoken->text;
        $ep_name //= $self->endpoint;
        if (!$code_name) {
            $app->log(
                error => sprintf(
                    "[build_dispatcher] POD error $label => %s <=> %s in %s line %u",
                    $if_name // '>rpcmethod-name-missing<',
                    '>sub-name-missing<',
                    $pkg_file,
                    $token->attr('start_line')
                ),
            );
            next;
        }
        $app->log(debug => "[build_dispatcher] $args{package}\::$code_name => $if_name ($ep_name)");
        next if $ep_name ne $self->endpoint;

        my $pkg = $args{package};
        if (my $handler = $pkg->can($code_name)) {
            $dispatch->{$if_name} = Dancer2::RPCPlugin::DispatchItem->new(
                package => $pkg,
                code    => $handler
            );
        } else {
            die "Handler not found for $if_name: $pkg\::$code_name doesn't seem to exist.\n";
        }

lib/Dancer2/RPCPlugin/DispatchFromPod.pm  view on Meta::CPAN

=head3 Parameters

=over

=item plugin_object => An instance of the current plugin

=item plugin => <jsonrpc|restrpc|xmlrpc>

=item packages => a list (ArrayRef) of package names to be parsed

=item endpoint => $endpoint

=back

=head2 $dfp->build_dispatch_table()

=head3 Parameters

None

=head3 Responses

lib/Dancer2/RPCPlugin/DispatchMethodList.pm  view on Meta::CPAN


Dancer2::RPCPlugin::DispatchMethodList - Class for maintaining a global methodlist.

=head1 SYNOPSIS

    use Dancer2::RPCPlugin::DispatchMethodList;
    my $methods = Dancer2::RPCPlugin::DispatchMethodList->new();

    $methods->set_partial(
        protocol => <jsonrpc|restrpc|xmlrpc>,
        endpoint => </configured>,
        methods  => [ @method_names ],
    );

    # Somewhere else
    my $dml = Dancer2::RPCPlugin::DispatchMethodList->new();
    my $methods = $dml->list_methods(<any|jsonrpc|restrpc|xmlrpc>);

=head1 DESCRIPTION

This class implements a singleton that can hold the collection of all method names.

lib/Dancer2/RPCPlugin/DispatchMethodList.pm  view on Meta::CPAN

=head2 $dml->set_partial(%parameters)

=head3 Parameters

Named, list:

=over

=item protocol => <jsonrpc|restrpc|xmlrpc>

=item endpoint => $endpoint

=item methods => \@method_list

=back

=head3 Responses

    $self

=cut

sub set_partial {
    my $self = shift;
    $self->validate_parameters(
        {
            $self->parameter(protocol => $self->Required, {store => \my $protocol}),
            $self->parameter(endpoint => $self->Required, {store => \my $endpoint}),
            $self->parameter(methods  => $self->Required, {store => \my $methods}),
        },
        { @_ }
    );

    $self->protocol->{$protocol}{$endpoint} = $methods;
    return $self;
}

=head2 $dml->list_methods(@parameters)

Method that returns information about the dispatch-table.

=head3 Parameters

Positional, list:

lib/Dancer2/RPCPlugin/DispatchMethodList.pm  view on Meta::CPAN

=item $protocol => undef || <any|jsonrpc|restrpc|xmlrpc>

=back

=head3 Responses

In case of no C<$protocol>:

    {
        xmlrpc => {
            $endpoint1 => [ list ],
            $endpoint2 => [ list ],
        },
        jsonrpc => {
            $endpoint1 => [ list ],
            $endpoint2 => [ list ],
        },
    }

In case of specified C<$protocol>:

    {
        $endpoint1 => [ list ],
        $endpoint2 => [ list ],
    }

=cut

sub list_methods {
    my $self = shift;
    $self->validate_positional_parameters(
        [ $self->parameter(any_protocol => $self->Optional, {store => \my $protocol}) ],
        [ @_ ]
    );

lib/Dancer2/RPCPlugin/ValidationTemplates.pm  view on Meta::CPAN

    my $plugin_config = Dict [
        publish      => Types::Standard::Optional [ Maybe [$publisher] ],
        arguments    => Types::Standard::Optional [ Maybe [ArrayRef] ],
        callback     => Types::Standard::Optional [CodeRef],
        code_wrapper => Types::Standard::Optional [CodeRef],
        plugin_args  => Types::Standard::Optional [HashRef],
    ];
    my $plugins = Dancer2::RPCPlugin::PluginNames->new->regex;
    my $any_plugin = qr{(?:any|$plugins)};
    return {
        endpoint => { type => StrMatch [qr{^ [\w/\\%-]+ $}x] },
        publish  => {
            type    => Maybe [$publisher],
            default => 'config'
        },
        arguments     => { type => Maybe [ArrayRef] },
        settings      => { type => Maybe [HashRef] },
        protocol      => { type => StrMatch [$plugins] },
        any_protocol  => { type => StrMatch [$any_plugin] },
        methods       => { type => ArrayRef [ StrMatch [qr{ . }x] ] },
        config        => { type => $plugin_config },

t/010-dispatch-from-config.t  view on Meta::CPAN

);
my $plugin = Test::MockObject->new->set_always(
    app => $app,
);

{
    note('Working dispatch table from configuration');
    my $builder = Dancer2::RPCPlugin::DispatchFromConfig->new(
        plugin_object => $plugin,
        plugin        => 'xmlrpc',
        endpoint      => '/xmlrpc',
        config        => {
            '/xmlrpc' => {
                'MyAppCode' => {
                    'system.ping'    => 'do_ping',
                    'system.version' => 'do_version',
                }
            }
        }
    );
    isa_ok($builder, 'Dancer2::RPCPlugin::DispatchFromConfig', "Builder")

t/010-dispatch-from-config.t  view on Meta::CPAN

}

{
    note('Adding non existing code, fails');
    like(
        exception {
            (
                my $builder = Dancer2::RPCPlugin::DispatchFromConfig->new(
                    plugin_object => $plugin,
                    plugin        => 'xmlrpc',
                    endpoint      => '/xmlrpc',
                    config        => {
                        '/xmlrpc' => {
                            'MyAppCode' => {
                                'system.nonexistent' => 'nonexistent',
                            }
                        }
                    },
                )
            )->build_dispatch_table();
        },

t/010-dispatch-from-config.t  view on Meta::CPAN

}

{
    note('Adding non existing package, fails');
    like(
        exception {
            (
                my $builder = Dancer2::RPCPlugin::DispatchFromConfig->new(
                    plugin_object => $plugin,
                    plugin        => 'xmlrpc',
                    endpoint      => '/xmlrpc',
                    config        => {
                        '/xmlrpc' => {
                            'MyNotExistingApp' => {
                                'system.nonexistent' => 'nonexistent',
                            }
                        }
                    },
                )
            )->build_dispatch_table();
        },

t/015-dispatch-from-pod.t  view on Meta::CPAN

    app => $app,
);

{
    note('Working dispatch table from POD');

    my $builder = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin        => 'jsonrpc',
        packages      => [qw/ MyAppCode /],
        endpoint      => '/testing',
    );
    isa_ok($builder, 'Dancer2::RPCPlugin::DispatchFromPod', 'Builder')
        or diag("\$builder isa: ", ref $builder);
    my $dispatch = $builder->build_dispatch_table();
    is_deeply(
        $dispatch,
        {
            'ping' => Dancer2::RPCPlugin::DispatchItem->new(
                code => MyAppCode->can('do_ping'),
                package => 'MyAppCode',

t/015-dispatch-from-pod.t  view on Meta::CPAN

{
    note('Adding non existing code, fails');

    like(
        exception {
            (
                my $builder = Dancer2::RPCPlugin::DispatchFromPod->new(
                    plugin_object => $plugin,
                    plugin        => 'jsonrpc',
                    packages      => [qw/ MyBogusApp /],
                    endpoint      => '/testing',
                )
            )->build_dispatch_table();
        },
        qr/Handler not found for bogus.nonexistent: MyBogusApp::nonexistent doesn't seem to exist/,
        "Setting a non-existent dispatch target throws an exception"
    );
}

{
    note('Adding non existing package, fails');
    like(
        exception {
            (
                my $builder = Dancer2::RPCPlugin::DispatchFromPod->new(
                    plugin_object => $plugin,
                    plugin        => 'jsonrpc',
                    packages      => [qw/ MyNotExistingApp /],
                    endpoint      => '/testing',
                )
            )->build_dispatch_table();
        },
        qr/Cannot load MyNotExistingApp .+ in build_dispatch_table_from_pod/s,
        "Using a non existing package throws an exception"
    );
}

{
    note('POD error in =for json');
    $logfile = "";
    like(
        exception {
            (
                my $builder = Dancer2::RPCPlugin::DispatchFromPod->new(
                    plugin_object => $plugin,
                    plugin        => 'jsonrpc',
                    packages      => [qw/ MyPoderrorApp /],
                    endpoint      => '/testing',
                )
            )->build_dispatch_table();
        },
        qr/Handler not found for method: MyPoderrorApp::code doesn't seem to exist/,
        "Ignore syntax-error in '=for jsonrpc/xmlrpc'"
    );
    like(
        $logfile,
        qr/^error .+ >rpcmethod-name-missing< <=> >sub-name-missing</m,
        "error log-message method and sub missing"

t/015-dispatch-from-pod.t  view on Meta::CPAN

        qr/^error .+ <=> >sub-name-missing</m,
        "error log-message sub missing"
    );
}

{
    my $xmlrpc = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin        => 'xmlrpc',
        packages      => [qw/ MixedEndpoints /],
        endpoint      => '/system',
    )->build_dispatch_table();

    my $system_call = Dancer2::RPCPlugin::DispatchItem->new(
        package => 'MixedEndpoints',
        code    => MixedEndpoints->can('call_for_system'),
    );
    my $any_call = Dancer2::RPCPlugin::DispatchItem->new(
        package => 'MixedEndpoints',
        code    => MixedEndpoints->can('call_for_all_endpoints'),
    );

    is_deeply(
        $xmlrpc,
        {
            'system.call' => $system_call,
            'any.call'    => $any_call,
        },
        "picked the /system call for xmlrpc"
    ) or diag(explain($xmlrpc));

    my $jsonrpc = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin => 'jsonrpc',
        packages => [ 'MixedEndpoints' ],
        endpoint => '/system',
    )->build_dispatch_table();
    is_deeply(
        $jsonrpc,
        {
            'system_call' => $system_call,
            'any_call'    => $any_call,
        },
        "picked the /system call for jsonrpc"
    ) or diag(explain($jsonrpc));

    my $restrpc = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin        => 'restrpc',
        packages      => ['MixedEndpoints'],
        endpoint      => '/system',
    )->build_dispatch_table();
    is_deeply(
        $restrpc,
        {
            'call'     => $system_call,
            'any-call' => $any_call,
        },
        "picked the /system call for restrpc"
    ) or diag(explain($restrpc));
}

{
    my $xmlrpc = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin        => 'xmlrpc',
        packages      => ['MixedEndpoints'],
        endpoint      => '/testing',
    )->build_dispatch_table();
    my $testing_call = Dancer2::RPCPlugin::DispatchItem->new(
        package => 'MixedEndpoints',
        code    => MixedEndpoints->can('call_for_testing'),
    );
    my $any_call = Dancer2::RPCPlugin::DispatchItem->new(
        package => 'MixedEndpoints',
        code    => MixedEndpoints->can('call_for_all_endpoints'),
    );

    is_deeply(
        $xmlrpc,
        {
            'testing.call' => $testing_call,
            'any.call'    => $any_call,
        },
        "picked the /testing call for xmlrpc"
    ) or diag(explain($xmlrpc));

    my $jsonrpc = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin        => 'jsonrpc',
        packages      => ['MixedEndpoints'],
        endpoint      => '/testing',
    )->build_dispatch_table();
    is_deeply(
        $jsonrpc,
        {
            'testing_call' => $testing_call,
            'any_call'    => $any_call,
        },
        "picked the /testing call for jsonrpc"
    ) or diag(explain($jsonrpc));

    my $restrpc = Dancer2::RPCPlugin::DispatchFromPod->new(
        plugin_object => $plugin,
        plugin        => 'restrpc',
        packages      => ['MixedEndpoints'],
        endpoint      => '/testing',
    )->build_dispatch_table();
    is_deeply(
        $restrpc,
        {
            'call'     => $testing_call,
            'any-call' => $any_call,
        },
        "picked the /testing call for restrpc"
    ) or diag(explain($restrpc));
}

t/030-dispatchmethodlist.t  view on Meta::CPAN

use t::Test::abeltje;

use Dancer2::RPCPlugin::DispatchMethodList;

note('Instantiate');
{
    my $dml = Dancer2::RPCPlugin::DispatchMethodList->new();
    isa_ok($dml, 'Dancer2::RPCPlugin::DispatchMethodList');

    my $methods = {
        jsonrpc => { '/endpoint_j' => [qw/ method1 method2 /] },
        xmlrpc  => { '/endpoint_x' => [qw/ method3 method4 /] },
    };
    for my $rpc (keys %$methods) {
        for my $ep (keys %{$methods->{$rpc}}) {
            $dml->set_partial(
                protocol => $rpc,
                endpoint => $ep,
                methods  => $methods->{$rpc}{$ep}
            );
        }
    }

    is_deeply(
        $dml->list_methods('any'),
        $methods,
        "all methods (any)"
    );

t/030-dispatchmethodlist.t  view on Meta::CPAN

        "list_methods(xmlrpc)"
    );
}

note('Instantiate again');
{
    my $dml = Dancer2::RPCPlugin::DispatchMethodList->new();
    isa_ok($dml, 'Dancer2::RPCPlugin::DispatchMethodList');

    my $methods = {
        jsonrpc => { '/endpoint_j' => [qw/ method1 method2 /] },
        xmlrpc  => { '/endpoint_x' => [qw/ method3 method4 /] },
    };

    is_deeply(
        $dml->list_methods('any'),
        $methods,
        "all methods (any)"
    );

    is_deeply(
        $dml->list_methods('jsonrpc'),

t/071-default-route-xmlrpc.t  view on Meta::CPAN

use Dancer2 qw/:syntax !pass !warning/;
use Plack::Test;

use Dancer2::RPCPlugin::DefaultRoute;
use Dancer2::Plugin::RPC::XMLRPC;
use RPC::XML;
use RPC::XML::ParserFactory;
$RPC::XML::ENCODING = 'utf-8';

my $ENDPOINT         = '/system/code_wrapper';
my $UNKNOWN_ENDPOINT = '/system/code_wrapper/undefined_endpoint';

use MyTest::API;
use MyTest::Client;

my $client = MyTest::Client->new(ping_value => 'pong');
my $dispatch = {
    'MyTest::API' => MyTest::API->new(test_client => $client),
};
my $config = {
    publish      => 'config',

t/071-default-route-xmlrpc.t  view on Meta::CPAN

        'RPC::XMLRPC' => {
            $ENDPOINT => {
                'MyTest::API' => {
                    'system_ping'      => 'rpc_ping',
                    'system.exception' => 'rpc_fail',
                }
            }
        }
    }
);
set( clients => { test_client => { endpoint => 'somewhere' } });

xmlrpc $ENDPOINT => $config;

my $app = main->to_app();
my $tester = Plack::Test->create($app);

note("Without catchall unknown endpoint errors");
{
    my $prefix = "Without catchall";
    my $response = _post($ENDPOINT);
    is($response->{status}, '200', "$prefix: Known endpoint returns 200 status");
    is_deeply(
        $response->{content},
        [ { result => 'pong' } ],
        "$prefix: Known route returns result"
    );

    $response = _post($UNKNOWN_ENDPOINT);
    is($response->{status}, 404, "$prefix: unknown endpoint returns 404 status");

    $response = _post($ENDPOINT, { method => 'system.pong'} );
    is($response->{status}, 404, "$prefix: Unknown method returns 404 status");
}

setup_default_route();

note('With catchall unknown endpoint errors');
{
    my $prefix = "With catchall";

    my $response = _post($ENDPOINT);
    is($response->{status}, 200, "$prefix: known endpoint returns 200 status");
    is_deeply(
        $response->{content},
        [ { result => 'pong'} ],
        "$prefix: Known route returns result"
    );

    $response = _post($UNKNOWN_ENDPOINT);
    is($response->{status}, 200, "$prefix: Unknown route returns 200 status");

    is_deeply(

t/071-default-route-xmlrpc.t  view on Meta::CPAN

    like(
        $response->{content}{faultString},
        qr/Method '.*' not found/,
        sprintf("RPC::XMLRPC: %s - %s", $prefix, $response->{content}{faultCode}),
    );
}

abeltje_done_testing();

sub _post {
    my ($endpoint, $body) = @_;
    my $parser = RPC::XML::ParserFactory->new();

    $body //= { method => 'system_ping' };
    my $xmlrpc_request = RPC::XML::request->new($body->{method})->as_string();
    my $request = HTTP::Request->new(
        POST => $endpoint,
        [ 'content-type' => 'text/xml' ],
        $xmlrpc_request,
    );
    my $response = $tester->request($request);

    my $dancer_response = {
        content_type => $response->header('content_type'),
        status       => $response->code,
        content      => $response->content,
    };

t/072-default-route-jsonrpc.t  view on Meta::CPAN

#! perl -I. -w
use t::Test::abeltje;

use Dancer2 qw/!pass !warning/;
use Plack::Test;

use Dancer2::RPCPlugin::DefaultRoute;
use Dancer2::Plugin::RPC::JSONRPC;

my $ENDPOINT         = '/system/code_wrapper';
my $UNKNOWN_ENDPOINT = '/system/code_wrapper/undefined_endpoint';

use MyTest::API;
use MyTest::Client;

my $client = MyTest::Client->new(ping_value => 'pong');
my $dispatch = {
    'MyTest::API' => MyTest::API->new(test_client => $client),
};
my $config = {
    publish      => 'config',

t/072-default-route-jsonrpc.t  view on Meta::CPAN

        'RPC::JSONRPC' => {
            $ENDPOINT => {
                'MyTest::API' => {
                    'system_ping'      => 'rpc_ping',
                    'system.exception' => 'rpc_fail',
                }
            }
        }
    }
);
set( clients => { test_client => { endpoint => 'somewhere' } });

jsonrpc $ENDPOINT => $config;

my $app = main->to_app();
my $tester = Plack::Test->create($app);

note("Without catchall unknown endpoint errors");
{
    my $prefix = "Without catchall";
    my $response = _post($ENDPOINT);
    is($response->{status}, 200, "$prefix: Known endpoint returns 200 status");
    is_deeply(
        $response->{content}{result},
        [ { result => 'pong' } ],
        "$prefix: Known route returns result"
    );

    $response = _post($UNKNOWN_ENDPOINT);
    is($response->{status}, 404, "$prefix: unknown endpoint returns 404 status");

    $response = _post(
        $ENDPOINT,
        { jsonrpc => "2.0", id => 4242, method => 'system_pong'}
    );
    is($response->{status}, 200, "$prefix: Unknown method returns 200 status");
    is(
        $response->{content}{error}{code},
        -32601,
        "$prefix: Unknown method returns -32601 code"
    );
}

setup_default_route();

note('With catchall unknown endpoint errors');
{
    my $prefix = "With catchall";

    my $response = _post($ENDPOINT);
    is($response->{status}, 200, "$prefix: known endpoint returns 200 status");
    is_deeply(
        $response->{content}{result},
        [ { result => 'pong'} ],
        "$prefix: Known route returns result"
    );

    $response = _post($UNKNOWN_ENDPOINT);
    is($response->{status}, 200, "$prefix: Unknown route returns 200 status");

    is_deeply(

t/072-default-route-jsonrpc.t  view on Meta::CPAN

    like(
        $response->{content}{error}{message},
        qr/Method '.*' not found/,
        sprintf("RPC::JSONRPC: %s - %s", $prefix, $response->{content}{error}{message}),
    );
}

abeltje_done_testing();

sub _post {
    my ($endpoint, $body) = @_;
    $body //= { jsonrpc => "2.0", id => "42", method => 'system_ping' };
    my $jsonrpc_request = to_json($body);

    my $request = HTTP::Request->new(
        POST => $endpoint,
        [ content_type => 'application/json' ],
        $jsonrpc_request,
    );
    my $response = $tester->request($request);

    my $dancer_response = {
        content_type => $response->header('content_type'),
        status       => $response->code,
        content      => $response->content,
    };

t/073-default-route-restrpc.t  view on Meta::CPAN

#! perl -I. -w
use t::Test::abeltje;

use Dancer2 qw/!pass !warning/;
use Dancer2::RPCPlugin::DefaultRoute;
use Dancer2::Plugin::RPC::RESTRPC;
use Plack::Test;

my $ENDPOINT         = '/system/code_wrapper';
my $UNKNOWN_ENDPOINT = '/system/code_wrapper/undefined_endpoint';


use MyTest::API;
use MyTest::Client;

my $client = MyTest::Client->new(ping_value => 'pong');
my $dispatch = {
    'MyTest::API' => MyTest::API->new(test_client => $client),
};
my $config = {

t/073-default-route-restrpc.t  view on Meta::CPAN

        'RPC::RESTRPC' => {
            $ENDPOINT => {
                'MyTest::API' => {
                    'system_ping'      => 'rpc_ping',
                    'system.exception' => 'rpc_fail',
                }
            }
        }
    }
);
set( clients => { test_client => { endpoint => 'somewhere' } });

restrpc $ENDPOINT => $config;

my $app = main->to_app();
my $tester = Plack::Test->create($app);

note("Without catchall unknown endpoint errors");
{
    my $prefix = "Without catchall";
    my $response = _post($ENDPOINT);
    is($response->{status}, 200, "$prefix: Known endpoint returns 200 status");
    is_deeply(
        $response->{content},
        [ { result => 'pong' } ],
        "$prefix: Known route returns result"
    );

    my $url = $UNKNOWN_ENDPOINT.'/system_ping';
    $response = _post($UNKNOWN_ENDPOINT);
    is($response->{status}, 404, "$prefix: unknown endpoint returns 404 status");

    $response = _post($ENDPOINT, { method => 'system.pong'} );
    $url = $ENDPOINT.'/system_pong';
    is($response->{status}, 404, "$prefix: Unknown method returns 404 status");
}

setup_default_route();

note('With catchall unknown endpoint errors');
{
    my $prefix = "With catchall";

    my $response = _post($ENDPOINT);
    is($response->{status}, 200, "$prefix: known endpoint returns 200 status");
    is_deeply(
        $response->{content},
        [ {result => 'pong'} ],
        "$prefix: Known route returns result"
    );

    my $url = $UNKNOWN_ENDPOINT.'/system_ping';
    $response = _post($UNKNOWN_ENDPOINT);
    is($response->{status}, 200, "$prefix: Unknown route returns 200 status");

t/073-default-route-restrpc.t  view on Meta::CPAN

    like(
        $error->{message},
        qr/Method '.*' not found/,
        sprintf("RPC::RESTRPC: %s - %s", $prefix, $error->{message})
    );
}

abeltje_done_testing();

sub _post {
    my ($endpoint, $body) = @_;
    my $url = sprintf("%s/%s", $endpoint, defined $body ? $body->{method} : 'system_ping');
    my $request = HTTP::Request->new(
        POST => $url,
        [ content_type => 'application/json' ],
        to_json( {} ),
    );
    my $response = $tester->request($request);

    my $dancer_response = {
        content_type => $response->header('content_type'),
        status       => $response->code,

t/090-rpcplugin-role.t  view on Meta::CPAN

    );
    is(MyConsumer::RESTRPC->rpcplugin_tag, 'restrpc', "CLASS->rpcplugin_tag()");
    is($tst->rpcplugin_tag, 'restrpc', "INSTANCE->rpcplugin_tag()");
}

{
    note('Create builder from config');
    my $tst = MyConsumer::RESTRPC->new();
    isa_ok($tst, 'MyConsumer::RESTRPC');
    my $builder = $tst->dispatch_builder(
        '/endpoint',
        undef,
        undef,
        {'/endpoint' => {'MyTestConfig' => {method1 => 'sub1'}}}
    );
    isa_ok($builder, 'CODE');
    my $dispatch = $builder->();
    is_deeply(
        $dispatch,
        {
            'method1' => Dancer2::RPCPlugin::DispatchItem->new(
                code => \&MyTestConfig::sub1,
                package => 'MyTestConfig',
            ),
        },
        "Dispatch from Config"
    );
}

{
    note('Create builder from POD');
    my $tst = MyConsumer::RESTRPC->new();
    isa_ok($tst, 'MyConsumer::RESTRPC');
    my $builder = $tst->dispatch_builder(
       '/endpoint',
       'pod',
       ['MyTestPod'],
    );
    isa_ok($builder, 'CODE');
    my $dispatch = $builder->();
    is_deeply(
        $dispatch,
        {
            'method2' => Dancer2::RPCPlugin::DispatchItem->new(
                code => \&MyTestPod::sub2,

t/090-rpcplugin-role.t  view on Meta::CPAN

        },
        "Dispatch from Pod"
    ) or diag(explain($dispatch));
}

{
    note('Dispatch from code');
    my $tst = MyConsumer::RESTRPC->new();
    isa_ok($tst, 'MyConsumer::RESTRPC');
    my $builder = $tst->dispatch_builder(
        '/endpoint',
        sub {
            return {
                method1 => Dancer2::RPCPlugin::DispatchItem->new(
                    code    => \&MyTestConfig::sub1,
                    package => 'MyTestConfig',
                ),
            }
        }
    );
    isa_ok($builder, 'CODE');

t/100-xmlrpc.t  view on Meta::CPAN


use HTTP::Request;
use RPC::XML::ParserFactory;

my $p = RPC::XML::ParserFactory->new();
my $app = MyXMLRPCApp->to_app();
my $tester = Plack::Test->create($app);

subtest "XMLRPC ping (POST)" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        [ 'Content-Type' => 'text/xml' ],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
<methodName>ping</methodName>
<params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);
    my $value = $p->parse($response->decoded_content)->value->value;
    is_deeply(
        $value,
        'pong',
        "ping"
    ) or diag(explain($value));
};

subtest "XMLRPC ping (GET)" => sub {
    my $request = HTTP::Request->new(
        GET => '/endpoint',
        [ 'Content-Type' => 'text/xml' ],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
<methodName>ping</methodName>
<params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);
    is($response->status_line, "404 Not Found", "Check method POST for xmlrpc");
};

subtest "XMLRPC wrong content-type (404)" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        [ 'Content-Type' => 'application/json', ],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
    <methodName>ping</methodName>
    <params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);
    is($response->status_line, "404 Not Found", "Check content-type xmlrpc")
        or diag(explain($response));
};

subtest "XMLRPC unknown rpc-method (404)" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        ['Content-Type' => 'text/xml'],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
    <methodName>system.doesnotexist</methodName>
    <params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);
    is($response->status_line, '404 Not Found', "Check known rpc-methods");
};

subtest "XMLRPC methodList(plugin => 'xmlrpc')" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        ['Content-Type' => 'text/xml'],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
    <methodName>methodList</methodName>
    <params>
      <param>
        <struct>
          <member>
            <name>plugin</name>

t/100-xmlrpc.t  view on Meta::CPAN

</methodCall>
        EOXML
    );
    my $response = $tester->request($request);
    is($response->status_line, '200 OK', "OK response");

    my $methods = $p->parse($response->decoded_content)->value->value;
    is_deeply(
        $methods,
        {
            '/endpoint' => [qw/
                methodList
                ping
                version
            /]
        },
        "methodList(plugin => 'xmlrpc')"
    ) or diag(explain($methods));
};

abeltje_done_testing();

BEGIN {
    package MyXMLRPCApp;
    use lib 'ex/';
    use Dancer2;
    use Dancer2::Plugin::RPC::XMLRPC;

    BEGIN { set(log => 'error') }
    xmlrpc '/endpoint' => {
        publish      => 'pod',
        arguments    => [qw/ MyAppCode /],
    };
    1;
}

t/110-xmlrpc-callbackfail.t  view on Meta::CPAN


use HTTP::Request;
use RPC::XML::ParserFactory;

my $p = RPC::XML::ParserFactory->new();
my $app = MyXMLRPCAppCallbackFail->to_app();
my $tester = Plack::Test->create($app);

subtest "XMLRPC Callback::Fail" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        [ 'Content-Type' => 'text/xml' ],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
<methodName>ping</methodName>
<params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);

t/110-xmlrpc-callbackfail.t  view on Meta::CPAN

abeltje_done_testing();

BEGIN {
    package MyXMLRPCAppCallbackFail;
    use lib 'ex/';
    use Dancer2;
    use Dancer2::Plugin::RPC::XMLRPC;
    use Dancer2::RPCPlugin::CallbackResultFactory;

    BEGIN { set(log => 'error') }
    xmlrpc '/endpoint' => {
        publish   => 'pod',
        arguments => [qw/ MyAppCode /],
        callback  => sub {
            return callback_fail(
                error_code    => -32760,
                error_message => "Callback failed",
            );
        },
    };
    1;

t/115-xmlrpc-callback-die.t  view on Meta::CPAN


use HTTP::Request;
use RPC::XML::ParserFactory;

my $p = RPC::XML::ParserFactory->new();
my $app = MyXMLRPCAppCallbackFail->to_app();
my $tester = Plack::Test->create($app);

subtest "XMLRPC Callback::Fail" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        [ 'Content-Type' => 'text/xml' ],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
<methodName>ping</methodName>
<params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);

t/115-xmlrpc-callback-die.t  view on Meta::CPAN

abeltje_done_testing();

BEGIN {
    package MyXMLRPCAppCallbackFail;
    use lib 'ex/';
    use Dancer2;
    use Dancer2::Plugin::RPC::XMLRPC;
    use Dancer2::RPCPlugin::CallbackResultFactory;

    BEGIN { set(log => 'error') }
    xmlrpc '/endpoint' => {
        publish   => 'pod',
        arguments => [qw/ MyAppCode /],
        callback  => sub {
            die "Callback die()s\n";
        },
    };
    1;
}

t/120-xmlrpc-codewrapper-die.t  view on Meta::CPAN


use HTTP::Request;
use RPC::XML::ParserFactory;

my $p = RPC::XML::ParserFactory->new();
my $app = MyXMLRPCAppCallbackFail->to_app();
my $tester = Plack::Test->create($app);

subtest "XMLRPC CodeWrapper" => sub {
    my $request = HTTP::Request->new(
        POST => '/endpoint',
        [ 'Content-Type' => 'text/xml' ],
        <<'        EOXML',
<?xml version="1.0"?>
<methodCall>
<methodName>ping</methodName>
<params/>
</methodCall>
        EOXML
    );
    my $response = $tester->request($request);

t/120-xmlrpc-codewrapper-die.t  view on Meta::CPAN

abeltje_done_testing();

BEGIN {
    package MyXMLRPCAppCallbackFail;
    use lib 'ex/';
    use Dancer2;
    use Dancer2::Plugin::RPC::XMLRPC;
    use Dancer2::RPCPlugin::CallbackResultFactory;

    BEGIN { set(log => 'error') }
    xmlrpc '/endpoint' => {
        publish      => 'pod',
        arguments    => [qw/ MyAppCode /],
        code_wrapper => sub {
            die "Codewrapper die()s\n";
        }
    };
    1;
}



( run in 0.443 second using v1.01-cache-2.11-cpan-b61123c0432 )