summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/MooseX/Net/API.pm141
-rw-r--r--lib/MooseX/Net/API/Meta/Class.pm34
-rw-r--r--lib/MooseX/Net/API/Meta/Method.pm18
-rw-r--r--lib/MooseX/Net/API/Test.pm120
-rw-r--r--t/00_compile.t4
-rw-r--r--t/01_basic.t3
-rw-r--r--t/10_mx_net_api_test.t43
-rw-r--r--t/lib/TestAPI.pm19
-rw-r--r--t/lib/TestApp.pm11
-rw-r--r--t/lib/TestApp/Controller/Root.pm16
10 files changed, 344 insertions, 65 deletions
diff --git a/lib/MooseX/Net/API.pm b/lib/MooseX/Net/API.pm
index e1ab774..4e1ae5e 100644
--- a/lib/MooseX/Net/API.pm
+++ b/lib/MooseX/Net/API.pm
@@ -1,13 +1,17 @@
package MooseX::Net::API;
-use Carp;
use URI;
-use HTTP::Request;
+use Carp;
use Try::Tiny;
+use HTTP::Request;
+
+use Moose;
use Moose::Exporter;
use MooseX::Net::API::Error;
-use MooseX::Net::API::Role::Deserialize;
+use MooseX::Net::API::Meta::Class;
+use MooseX::Net::API::Meta::Method;
use MooseX::Net::API::Role::Serialize;
+use MooseX::Net::API::Role::Deserialize;
our $VERSION = '0.01';
@@ -30,6 +34,16 @@ my $reverse_content_type = {
Moose::Exporter->setup_import_methods(
with_caller => [qw/net_api_method net_api_declare/], );
+sub init_meta {
+ my ( $me, %options ) = @_;
+
+ my $for = $options{for_class};
+ Moose::Util::MetaRole::apply_metaclass_roles(
+ for_class => $for,
+ metaclass_roles => ['MooseX::Net::API::Meta::Class'],
+ );
+}
+
my ( $do_auth, $auth_method, $deserialize_method );
sub net_api_declare {
@@ -39,18 +53,13 @@ sub net_api_declare {
my $class = Moose::Meta::Class->initialize($caller);
- if ( !$options{base_url} ) {
- croak "base_url is missing in your api declaration";
- }
- else {
- $class->add_attribute(
- 'api_base_url',
- is => 'ro',
- isa => 'Str',
- lazy => 1,
- default => delete $options{base_url}
- );
- }
+ $class->add_attribute(
+ 'api_base_url',
+ is => 'rw',
+ isa => 'Str',
+ lazy => 1,
+ default => delete $options{base_url} || '',
+ );
if ( !$options{format} ) {
croak "format is missing in your api declaration";
@@ -180,37 +189,8 @@ sub net_api_method {
my $format = $self->api_format();
$url .= "." . $format if ( $self->api_format_mode() eq 'append' );
my $uri = URI->new($url);
+ my $res = _request( $self, $format, \%options, $uri, \%args );
- my $req;
- my $method = $options{method};
- if ( $method =~ /^(?:GET|DELETE)$/ || $options{params_in_url} ) {
- $uri->query_form(%args);
- $req = HTTP::Request->new( $method => $uri );
- }
- elsif ( $method =~ /^(?:POST|PUT)$/ ) {
- $req = HTTP::Request->new( $method => $uri );
- # XXX GNI
- use JSON::XS;
- $req->content( encode_json \%args );
- }
- else {
- croak "$method is not defined";
- }
-
- # XXX check presence content type
- $req->header( 'Content-Type' => $list_content_type->{$format} )
- if $self->api_format_mode eq 'content-type';
-
- if ($do_auth) {
- if ($auth_method) {
- $req = $self->$auth_method($req);
- }
- else {
- $req = _do_authentication( $self, $req );
- }
- }
-
- my $res = $self->useragent->request($req);
my $content_type = $res->headers->{"content-type"};
$content_type =~ s/(;.+)$//;
@@ -249,6 +229,7 @@ sub net_api_method {
%options,
),
);
+ $class->_add_api_method($name);
}
sub _add_useragent {
@@ -277,6 +258,41 @@ sub _add_useragent {
);
}
+sub _request {
+ my ( $self, $format, $options, $uri, $args ) = @_;
+
+ my $req;
+ my $method = $options->{method};
+ if ( $method =~ /^(?:GET|DELETE)$/ || $options->{params_in_url} ) {
+ $uri->query_form(%$args);
+ $req = HTTP::Request->new( $method => $uri );
+ }
+ elsif ( $method =~ /^(?:POST|PUT)$/ ) {
+ $req = HTTP::Request->new( $method => $uri );
+
+ # XXX proper serialisation
+ use JSON::XS;
+ $req->content( encode_json $args );
+ }
+ else {
+ croak "$method is not defined";
+ }
+
+ $req->header( 'Content-Type' => $list_content_type->{$format} )
+ if $self->api_format_mode eq 'content-type';
+
+ if ($do_auth) {
+ if ($auth_method) {
+ $req = $self->$auth_method($req);
+ }
+ else {
+ $req = _do_authentication( $self, $req );
+ }
+ }
+
+ return $self->useragent->request($req);
+}
+
sub _do_authentication {
my ( $caller, $req ) = @_;
$req->headers->authorization_basic( $caller->api_username,
@@ -305,23 +321,6 @@ sub _do_deserialization {
}
}
-package MooseX::Net::API::Meta::Method;
-
-use Moose;
-extends 'Moose::Meta::Method';
-
-has description => ( is => 'ro', isa => 'Str' );
-has path => ( is => 'ro', isa => 'Str', required => 1 );
-has method => ( is => 'ro', isa => 'Str', required => 1 );
-has params => ( is => 'ro', isa => 'ArrayRef', required => 0 );
-has required => ( is => 'ro', isa => 'ArrayRef', required => 0 );
-
-sub new {
- my $class = shift;
- my %args = @_;
- $class->SUPER::wrap(@_);
-}
-
1;
__END__
@@ -338,6 +337,7 @@ MooseX::Net::API - Easily create client for net API
# we declare an API, the base_url is http://exemple.com/api
# the format is json and it will be happened to the query
+ # You can set base_url later, calling $my_obj->api_base_url('http://..')
net_api_declare my_api => (
base_url => 'http://exemple.com/api',
format => 'json',
@@ -426,6 +426,23 @@ HTTP method (GET, POST, PUT, DELETE)
path of the query.
+If you defined your path and params like this
+
+ net_api_method user_comments => (
+ ...
+ path => '/user/$user/list/$date/',
+ params => [qw/user date foo bar/],
+ ...
+ );
+
+and you call
+
+ $obj->user_comments(user => 'franck', date => 'today', foo => 1, bar => 2);
+
+the url generetad will look like
+
+ /user/franck/list/today/?foo=1&bar=2
+
=item B<params> [arrayref]
list of params.
diff --git a/lib/MooseX/Net/API/Meta/Class.pm b/lib/MooseX/Net/API/Meta/Class.pm
new file mode 100644
index 0000000..80075f8
--- /dev/null
+++ b/lib/MooseX/Net/API/Meta/Class.pm
@@ -0,0 +1,34 @@
+package MooseX::Net::API::Meta::Class;
+
+use Moose::Role;
+use Moose::Meta::Class;
+use MooseX::Types::Moose qw(Str ArrayRef ClassName Object);
+
+has local_api_methods => (
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Str],
+ required => 1,
+ default => sub { [] },
+ auto_deref => 1,
+ handles => { '_add_api_method' => 'push' },
+);
+has local_api_test_methods => (
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Str],
+ required => 1,
+ default => sub { [] },
+ auto_deref => 1,
+ handles => { '_add_api_test_method' => 'push' },
+);
+
+sub _build_meta_class {
+ my $self = shift;
+ return Moose::Meta::Class->create_anon_class(
+ superclasses => [ $self->method_metaclass ],
+ cache => 1,
+ );
+}
+
+1;
diff --git a/lib/MooseX/Net/API/Meta/Method.pm b/lib/MooseX/Net/API/Meta/Method.pm
new file mode 100644
index 0000000..74f9a07
--- /dev/null
+++ b/lib/MooseX/Net/API/Meta/Method.pm
@@ -0,0 +1,18 @@
+package MooseX::Net::API::Meta::Method;
+
+use Moose;
+extends 'Moose::Meta::Method';
+
+has description => ( is => 'ro', isa => 'Str' );
+has path => ( is => 'ro', isa => 'Str', required => 1 );
+has method => ( is => 'ro', isa => 'Str', required => 1 );
+has params => ( is => 'ro', isa => 'ArrayRef', required => 0 );
+has required => ( is => 'ro', isa => 'ArrayRef', required => 0 );
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ $class->SUPER::wrap(@_);
+}
+
+1;
diff --git a/lib/MooseX/Net/API/Test.pm b/lib/MooseX/Net/API/Test.pm
index 2f2e428..e991b7f 100644
--- a/lib/MooseX/Net/API/Test.pm
+++ b/lib/MooseX/Net/API/Test.pm
@@ -1,13 +1,131 @@
package MooseX::Net::API::Test;
+use lib ('t/lib');
+use Try::Tiny;
+
+use Test::More;
+use Moose;
use Moose::Exporter;
+use MooseX::Net::API::Meta::Class;
+use MooseX::Net::API::Meta::Method;
+
+Moose::Exporter->setup_import_methods(
+ with_caller => [qw/test_api_method test_api_declare run/] );
+
+my $api_to_test;
+
+sub init_meta {
+ my ( $me, %options ) = @_;
+
+ my $for = $options{for_class};
+ Moose::Util::MetaRole::apply_metaclass_roles(
+ for_class => $for,
+ metaclass_roles => ['MooseX::Net::API::Meta::Class'],
+ );
+}
+
+my $list_content_type = {
+ 'json' => 'application/json',
+ 'yaml' => 'text/x-yaml',
+ 'xml' => 'text/xml',
+};
+
+my $tests_count = 0;
+
+sub test_api_declare {
+ my $caller = shift;
+ my $name = shift;
+ my %options = @_;
+
+ unless ( Class::MOP::is_class_loaded($name) ) {
+ Class::MOP::load_class($name);
+ }
-Moose::Exporter->setup_import_methods( with_caller => [qw/test_api_method/] );
+ $api_to_test = $name;
+
+ if ( $options{catalyst} ) {
+ my $app = $options{catalyst_app_name};
+
+ Class::MOP::load_class("HTTP::Request");
+ Class::MOP::load_class("Catalyst::Test");
+
+ Catalyst::Test->import($app);
+
+ my $res = __PACKAGE__->meta->remove_method('_request');
+ MooseX::Net::API->meta->add_method(
+ '_request' => sub {
+ my ( $class, $format, $options, $uri, $args ) = @_;
+ my $method = $options->{method};
+
+ my $res;
+ if ( $method =~ /^(?:GET|DELETE)$/
+ || $options->{params_in_url} )
+ {
+ $uri->query_form(%$args);
+ my $req = HTTP::Request->new( $method => $uri );
+ $req->header(
+ 'Content-Type' => $list_content_type->{$format} );
+ $res = request($req);
+ }
+ else {
+ my $req = HTTP::Request->new( $method => $uri );
+ $req->header(
+ 'Content-Type' => $list_content_type->{$format} );
+ $req->header( 'Content' => Dump $args);
+ $res = request($req);
+ }
+ return $res;
+ }
+ );
+ }
+}
sub test_api_method {
my $caller = shift;
my $name = shift;
my %options = @_;
+
+ my $meta = $api_to_test->meta;
+ my $method = $meta->find_method_by_name($name);
+
+ if ( !$method ) {
+ die "method $name does not exists\n";
+ }
+
+ my $class = Moose::Meta::Class->initialize($caller);
+ foreach my $test_name ( keys %{ $options{tests} } ) {
+ foreach my $test ( @{ $options{tests}{$test_name} } ) {
+ __PACKAGE__->meta->add_method(
+ $test_name => sub {
+ my $res = $method->execute( $api_to_test->new );
+ if (ref $test eq 'HASH') {
+ my $action = $test->{test};
+ my $result = $test->{expected};
+ # XXX sucky sucky sucky
+ if ( $action eq 'is_deeply' ) {
+ is_deeply( $res, $result );
+ }
+ }else{
+ if ($test eq 'ok') {
+ ok $res;
+ }
+ }
+ }
+ );
+ $class->_add_api_test_method($test_name);
+ }
+ }
+}
+
+sub run {
+ my $caller = shift;
+
+ my $class = Moose::Meta::Class->initialize($caller);
+ my @test_methods = $class->local_api_test_methods();
+ foreach my $m (@test_methods) {
+ my $method = __PACKAGE__->meta->find_method_by_name($m);
+ $method->execute();
+ }
}
1;
diff --git a/t/00_compile.t b/t/00_compile.t
index 86438a0..10b03a4 100644
--- a/t/00_compile.t
+++ b/t/00_compile.t
@@ -1,4 +1,4 @@
use strict;
-use Test::More tests => 1;
+use Test::More tests => 2;
-BEGIN { use_ok 'MooseX::Net::API' }
+BEGIN { use_ok 'MooseX::Net::API'; use_ok 'MooseX::Net::API::Test' }
diff --git a/t/01_basic.t b/t/01_basic.t
index 9c108d2..471f3dd 100644
--- a/t/01_basic.t
+++ b/t/01_basic.t
@@ -29,4 +29,7 @@ throws_ok {
}
qr/foo is not declared as a param/, "... check declared params";
+ok my @methods = $obj->meta->local_api_methods(), '... get api methods';
+is scalar @methods, 3, '... got 3 methods in our API';
+
done_testing;
diff --git a/t/10_mx_net_api_test.t b/t/10_mx_net_api_test.t
new file mode 100644
index 0000000..96bf2be
--- /dev/null
+++ b/t/10_mx_net_api_test.t
@@ -0,0 +1,43 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN {
+ plan skip_all => 'requires Catalyst::Action::REST'
+ unless eval { require Catalyst::Action::REST };
+}
+
+{
+
+ package catalysttestapi;
+ use Moose;
+ use MooseX::Net::API::Test;
+
+ test_api_declare 'TestAPI' => (
+ catalyst => 1,
+ catalyst_app_name => 'TestApp'
+ );
+
+ test_api_method foo => (
+ tests => {
+ simple => [
+ {
+ # pouvoir surcharger
+ test => 'is_deeply',
+ expected => { status => 1 }
+ },
+ 'ok',
+ ]
+ }
+ );
+}
+
+#content_like => [ { expected => qr/status: 1/ }, ],
+#action_ok => [],
+#action_redirect => [],
+#action_notfound => [],
+#contenttype_is => [],
+
+catalysttestapi->run();
+
+done_testing;
diff --git a/t/lib/TestAPI.pm b/t/lib/TestAPI.pm
new file mode 100644
index 0000000..1f58351
--- /dev/null
+++ b/t/lib/TestAPI.pm
@@ -0,0 +1,19 @@
+package TestAPI;
+use Moose;
+use MooseX::Net::API;
+
+net_api_declare fake_api => (
+ base_url => 'http://localhost/root',
+ format => 'json',
+ format_mode => 'content-type',
+ require_authentication => 0,
+);
+
+net_api_method foo => (
+ description => 'this does foo',
+ method => 'GET',
+ path => '/foo/',
+);
+
+1;
+
diff --git a/t/lib/TestApp.pm b/t/lib/TestApp.pm
new file mode 100644
index 0000000..69ec93d
--- /dev/null
+++ b/t/lib/TestApp.pm
@@ -0,0 +1,11 @@
+package TestApp;
+use strict;
+use warnings;
+
+use Catalyst;
+
+__PACKAGE__->config( name => 'TestApp', );
+
+__PACKAGE__->setup;
+
+1;
diff --git a/t/lib/TestApp/Controller/Root.pm b/t/lib/TestApp/Controller/Root.pm
new file mode 100644
index 0000000..7ea3f25
--- /dev/null
+++ b/t/lib/TestApp/Controller/Root.pm
@@ -0,0 +1,16 @@
+package TestApp::Controller::Root;
+
+use strict;
+use warnings;
+use base qw/Catalyst::Controller::REST/;
+
+sub foo : Local : ActionClass('REST') {
+ my ( $self, $c ) = @_;
+}
+
+sub foo_GET {
+ my ( $self, $c ) = @_;
+ $self->status_ok( $c, entity => { status => 1 } );
+}
+
+1;