summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--eg/api.pl2
-rw-r--r--lib/Net/HTTP/Spore.pm108
-rw-r--r--lib/Net/HTTP/Spore/Meta.pm1
-rw-r--r--lib/Net/HTTP/Spore/Meta/Class.pm2
-rw-r--r--lib/Net/HTTP/Spore/Meta/Method.pm51
-rw-r--r--lib/Net/HTTP/Spore/Meta/Method/Spore.pm6
-rw-r--r--lib/Net/HTTP/Spore/Role/Debug.pm14
-rw-r--r--lib/Net/HTTP/Spore/Role/Description.pm7
-rw-r--r--lib/Net/HTTP/Spore/Role/Request.pm1
-rw-r--r--t/specs/api2.json20
-rw-r--r--t/spore-method/payload.t33
-rw-r--r--t/spore/01_new_from_string.t68
12 files changed, 261 insertions, 52 deletions
diff --git a/eg/api.pl b/eg/api.pl
index 9fd3bee..60559ff 100644
--- a/eg/api.pl
+++ b/eg/api.pl
@@ -8,6 +8,6 @@ my $api = Net::HTTP::Spore->new_from_spec(shift);
$api->enable('Net::HTTP::Spore::Middleware::Format::JSON');
-my ( $content, $result ) = $api->user_information( format => 'json', username => 'franckcuny' );
+my ( $content, $result ) = $api->get_info( format => 'json', username => 'franckcuny' );
say "name => ". $content->{body}->{user}->{name};
diff --git a/lib/Net/HTTP/Spore.pm b/lib/Net/HTTP/Spore.pm
index 34f4740..03606a0 100644
--- a/lib/Net/HTTP/Spore.pm
+++ b/lib/Net/HTTP/Spore.pm
@@ -8,70 +8,124 @@ use IO::All;
use JSON;
use Carp;
use Try::Tiny;
+use Scalar::Util;
use Net::HTTP::Spore::Core;
our $VERSION = 0.01;
+# XXX should we let the possibility to override this super class, or add
+# another superclasses?
+
sub new_from_string {
my ($class, $string, %args) = @_;
- my $spec;
+ my $spore_class =
+ Class::MOP::Class->create_anon_class(
+ superclasses => ['Net::HTTP::Spore::Core'] );
- try {
- $spec = JSON::decode_json($string);
- }catch{
- Carp::confess("unable to parse JSON spec: ".$_);
- };
+ my $spore_object = _attach_spec_to_class($string, \%args, $spore_class);
+
+ return $spore_object;
+}
+
+sub new_from_strings {
+ my $class = shift;
- my ( $spore_class, $spore_object );
- # XXX should we let the possibility to override this super class, or add
- # another superclasses?
+ my $opts;
+ if (ref ($_[-1]) eq 'HASH') {
+ $opts = pop @_;
+ }
+ my @strings = @_;
- $spore_class =
+ my $spore_class =
Class::MOP::Class->create_anon_class(
superclasses => ['Net::HTTP::Spore::Core'] );
+ my $spore_object = undef;
+ foreach my $string (@strings) {
+ $spore_object = _attach_spec_to_class($string, $opts, $spore_class, $spore_object);
+ }
+ return $spore_object;
+}
+
+sub new_from_spec {
+ my ( $class, $spec_file, %args ) = @_;
+
+ Carp::confess("specification file is missing") unless $spec_file;
+
+ my $content = _read_spec($spec_file);
+
+ $class->new_from_string( $content, %args );
+}
+
+sub new_from_specs {
+ my $class = shift;
+
+ my $opts;
+ if (ref ($_[-1]) eq 'HASH') {
+ $opts = pop @_;
+ }
+ my @specs = @_;
+
+ my @strings;
+ foreach my $spec (@specs) {
+ push @strings,_read_spec($spec);
+ }
+
+ $class->new_from_strings(@strings, $opts);
+}
+
+sub _attach_spec_to_class {
+ my ( $string, $opts, $class, $object ) = @_;
+
+ my $spec;
+ try {
+ $spec = JSON::decode_json($string);
+ }
+ catch {
+ Carp::confess( "unable to parse JSON spec: " . $_ );
+ };
+
try {
my $base_url;
- if ( $spec->{base_url} && !$args{base_url} ) {
- $args{base_url} = $spec->{base_url};
+ if ( $spec->{base_url} && !$opts->{base_url} ) {
+ $opts->{base_url} = $spec->{base_url};
}
- elsif ( !$args{base_url} ) {
+ elsif ( !$opts->{base_url} ) {
die "base_url is missing!";
}
if ( $spec->{formats} ) {
- $args{formats} = $spec->{formats};
+ $opts->{formats} = $spec->{formats};
}
if ( $spec->{authentication} ) {
- $args{authentication} = $spec->{authentication};
+ $opts->{authentication} = $spec->{authentication};
}
- $spore_object = $spore_class->new_object(%args);
- $spore_object = _add_methods( $spore_object, $spec->{methods} );
-
+ if ( !$object ) {
+ $object = $class->new_object(%$opts);
+ }
+ $object = _add_methods( $object, $spec->{methods} );
}
catch {
Carp::confess( "unable to create new Net::HTTP::Spore object: " . $_ );
};
- return $spore_object;
+ return $object;
}
-sub new_from_spec {
- my ( $class, $spec_file, %args ) = @_;
-
- Carp::confess("specification file is missing") unless $spec_file;
+sub _read_spec {
+ my $spec_file = shift;
- my ( $content, $spec );
+ my $content;
if ( $spec_file =~ m!^http(s)?://! ) {
my $uri = URI->new($spec_file);
- my $req = HTTP::Request->new(GET => $spec_file);
+ my $req = HTTP::Request->new( GET => $spec_file );
my $ua = LWP::UserAgent->new();
- my $res = $ua->request( $req );
+ my $res = $ua->request($req);
$content = $res->content;
}
else {
@@ -81,7 +135,7 @@ sub new_from_spec {
$content < io($spec_file);
}
- $class->new_from_string( $content, %args );
+ return $content;
}
sub _add_methods {
diff --git a/lib/Net/HTTP/Spore/Meta.pm b/lib/Net/HTTP/Spore/Meta.pm
index ec773b6..14b4266 100644
--- a/lib/Net/HTTP/Spore/Meta.pm
+++ b/lib/Net/HTTP/Spore/Meta.pm
@@ -36,6 +36,7 @@ sub init_meta {
for => $for,
roles => [
qw/
+ Net::HTTP::Spore::Role::Debug
Net::HTTP::Spore::Role::Description
Net::HTTP::Spore::Role::UserAgent
Net::HTTP::Spore::Role::Request
diff --git a/lib/Net/HTTP/Spore/Meta/Class.pm b/lib/Net/HTTP/Spore/Meta/Class.pm
index 4ddd5c6..7571305 100644
--- a/lib/Net/HTTP/Spore/Meta/Class.pm
+++ b/lib/Net/HTTP/Spore/Meta/Class.pm
@@ -4,7 +4,7 @@ package Net::HTTP::Spore::Meta::Class;
use Moose::Role;
-with qw/Net::HTTP::Spore::Meta::Method::Spore/;
+with qw/Net::HTTP::Spore::Meta::Method::Spore Net::HTTP::Spore::Role::Debug/;
1;
diff --git a/lib/Net/HTTP/Spore/Meta/Method.pm b/lib/Net/HTTP/Spore/Meta/Method.pm
index b546d74..10132ed 100644
--- a/lib/Net/HTTP/Spore/Meta/Method.pm
+++ b/lib/Net/HTTP/Spore/Meta/Method.pm
@@ -50,12 +50,20 @@ has path => ( is => 'ro', isa => 'UriPath', required => 1 );
has method => ( is => 'ro', isa => 'Method', required => 1 );
has description => ( is => 'ro', isa => 'Str', predicate => 'has_description' );
+has required_payload => (
+ is => 'ro',
+ isa => 'Boolean',
+ predicate => 'payload_is_required',
+ lazy => 1,
+ default => 0,
+ coerce => 1,
+);
has authentication => (
is => 'ro',
isa => 'Boolean',
predicate => 'has_authentication',
default => 0,
- coerce => 1,
+ coerce => 1,
);
has base_url => (
is => 'ro',
@@ -82,17 +90,24 @@ has expected_status => (
handles => { find_expected_status => 'grep', },
);
has optional_params => (
- traits => ['Array'],
- is => 'ro',
- isa => ArrayRef [Str],
- predicate => 'has_optional_params',
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Str],
+ predicate => 'has_optional_params',
auto_deref => 1,
);
has required_params => (
- traits => ['Array'],
- is => 'ro',
- isa => ArrayRef [Str],
- predicate => 'has_required_params',
+ traits => ['Array'],
+ is => 'ro',
+ isa => ArrayRef [Str],
+ predicate => 'has_required_params',
+ auto_deref => 1,
+);
+has form_data => (
+ traits => ['Hash'],
+ is => 'ro',
+ isa => 'HashRef',
+ predicate => 'has_form_data',
auto_deref => 1,
);
has documentation => (
@@ -128,6 +143,24 @@ sub wrap {
? delete $method_args{spore_payload}
: delete $method_args{payload};
+ if ( $payload
+ && ( $method->method ne 'POST' || $method->method ne 'PUT' ) )
+ {
+ die Net::HTTP::Spore::Response->new( 599, [],
+ { error => "payload requires a PUT or POST method" },
+ );
+ }
+
+ if ( $method->payload_is_required && !$payload ) {
+ die Net::HTTP::Spore::Response->new(
+ 599,
+ [],
+ {
+ error => "this method require a payload, and no payload is provided",
+ }
+ );
+ }
+
if ($method->has_required_params) {
foreach my $required ( $method->required_params ) {
if ( !grep { $required eq $_ } keys %method_args ) {
diff --git a/lib/Net/HTTP/Spore/Meta/Method/Spore.pm b/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
index 1cea574..a616149 100644
--- a/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
+++ b/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
@@ -54,6 +54,12 @@ sub add_spore_method {
my $code = delete $options{code};
+ $meta->_trace_msg( '-> attach '
+ . $name . ' ('
+ . $options{method} . ' => '
+ . $options{path}
+ . ')' );
+
$meta->add_method(
$name,
Net::HTTP::Spore::Meta::Method->wrap(
diff --git a/lib/Net/HTTP/Spore/Role/Debug.pm b/lib/Net/HTTP/Spore/Role/Debug.pm
new file mode 100644
index 0000000..772373a
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Role/Debug.pm
@@ -0,0 +1,14 @@
+package Net::HTTP::Spore::Role::Debug;
+
+use Moose::Role;
+
+has trace => (
+ is => 'rw',
+ isa => 'Bool',
+ lazy => 1,
+ default => sub { $ENV{SPORE_TRACE} ? 1 : 0; }
+);
+
+sub _trace_msg { print STDOUT $_[1]."\n" if $_[0]->trace; }
+
+1;
diff --git a/lib/Net/HTTP/Spore/Role/Description.pm b/lib/Net/HTTP/Spore/Role/Description.pm
index 213955d..2723fef 100644
--- a/lib/Net/HTTP/Spore/Role/Description.pm
+++ b/lib/Net/HTTP/Spore/Role/Description.pm
@@ -24,4 +24,11 @@ has authentication => (
predicate => 'has_authentication',
);
+has expected_status => (
+ is => 'rw',
+ isa => 'Array',
+ lazy => 1,
+ default => sub { [] },
+);
+
1;
diff --git a/lib/Net/HTTP/Spore/Role/Request.pm b/lib/Net/HTTP/Spore/Role/Request.pm
index d22a721..a0d61b1 100644
--- a/lib/Net/HTTP/Spore/Role/Request.pm
+++ b/lib/Net/HTTP/Spore/Role/Request.pm
@@ -66,6 +66,7 @@ sub _request {
$result->headers,
$result->content,
);
+
return $response;
}
diff --git a/t/specs/api2.json b/t/specs/api2.json
new file mode 100644
index 0000000..7341405
--- /dev/null
+++ b/t/specs/api2.json
@@ -0,0 +1,20 @@
+{
+ "name": "Test API",
+ "methods" : {
+ "get_projects" : {
+ "authentication": true,
+ "path" : "/projects/show",
+ "method" : "GET",
+ "optional_params" : [
+ "name"
+ ]
+ },
+ "get_project_info" : {
+ "required_params" : [
+ "project"
+ ],
+ "path" : "/project/show",
+ "method" : "GET"
+ }
+ }
+}
diff --git a/t/spore-method/payload.t b/t/spore-method/payload.t
new file mode 100644
index 0000000..483d9cc
--- /dev/null
+++ b/t/spore-method/payload.t
@@ -0,0 +1,33 @@
+use strict;
+use warnings;
+use Test::More tests => 4;
+
+use Test::Exception;
+
+use JSON;
+use Net::HTTP::Spore;
+
+my $api_with_payload = {
+ base_url => 'foo',
+ methods => {
+ create_user => {
+ method => 'POST',
+ path => '/user',
+ required_payload => 1,
+ },
+ list_user => {
+ method => 'GET',
+ path => '/user',
+ }
+ },
+};
+
+my $obj =
+ Net::HTTP::Spore->new_from_string( JSON::encode_json($api_with_payload),
+ base_url => 'http://localhost' );
+
+dies_ok { $obj->create_user(); };
+like $@->body->{error}, qr/this method require a payload/;
+
+dies_ok { $obj->list_user( payload => {} ) };
+like $@->body->{error}, qr/payload requires a PUT or POST method/;
diff --git a/t/spore/01_new_from_string.t b/t/spore/01_new_from_string.t
index e9d9357..6a21994 100644
--- a/t/spore/01_new_from_string.t
+++ b/t/spore/01_new_from_string.t
@@ -3,18 +3,39 @@ use warnings;
use Test::More;
use Test::Exception;
-plan tests => 14;
+plan tests => 27;
+use JSON;
use IO::All;
use Net::HTTP::Spore;
my $api_spec = 't/specs/api.json';
+my $api2_spec = 't/specs/api2.json';
+
my %args = ( base_url => 'http://localhost/', );
my $github_spec =
"http://github.com/franckcuny/spore/raw/master/services/github.json";
-my $content < io($api_spec);
+my $api_ok = {
+ base_url => "http://services.org/restapi",
+ methods => { get_info => { method => 'GET', path => '/show' } },
+};
+
+my $second_api = {
+ base_url => "http://services.org/restapi",
+ methods => { list_users => { method => 'GET', path => '/users' } },
+};
+
+my $api_without_path = {
+ base_url => "http://services.org/restapi",
+ methods => { get_info => { method => 'GET' } },
+};
+
+my $api_without_method = {
+ base_url => "http://services.org/restapi",
+ methods => { get_info => { method => 'PET', path => '/show' } },
+};
dies_ok { Net::HTTP::Spore->new_from_spec };
like $@, qr/specification file is missing/;
@@ -22,31 +43,50 @@ like $@, qr/specification file is missing/;
dies_ok { Net::HTTP::Spore->new_from_spec( "/foo/bar/baz", ) };
like $@, qr/does not exists/;
-dies_ok { Net::HTTP::Spore->new_from_spec( $api_spec, ) };
+dies_ok { Net::HTTP::Spore->new_from_spec( $api_spec ) };
like $@, qr/base_url is missing/;
ok my $client = Net::HTTP::Spore->new_from_spec( $api_spec, %args );
-ok $client = Net::HTTP::Spore->new_from_string( $content, %args );
+ok $client =
+ Net::HTTP::Spore->new_from_string( JSON::encode_json($api_ok), %args );
+ok $client->meta->_find_spore_method_by_name(sub{/^get_info$/});
SKIP: {
- skip "require RUN_HTTP_TEST", 1 unless $ENV{RUN_HTTP_TEST};
+ skip "require RUN_HTTP_TEST", 2 unless $ENV{RUN_HTTP_TEST};
ok $client = Net::HTTP::Spore->new_from_spec( $github_spec, %args );
+ ok $client->meta->_find_spore_method_by_name(sub{/^user_search$/});
}
dies_ok {
- Net::HTTP::Spore->new_from_string(
-'{"base_url" : "http://services.org/restapi/","methods" : { "get_info" : { "method" : "GET" } } }'
- );
+ Net::HTTP::Spore->new_from_string( JSON::encode_json($api_without_path) );
};
like $@, qr/Attribute \(path\) is required/;
dies_ok {
- Net::HTTP::Spore->new_from_string(
-'{"base_url" : "http://services.org/restapi/","methods" : { "get_info" : { "method" : "PET", "path":"/info" } } }'
- );
+ Net::HTTP::Spore->new_from_string(JSON::encode_json($api_without_method));
};
like $@, qr/Attribute \(method\) does not pass the type constraint/;
-ok $client = Net::HTTP::Spore->new_from_string(
-'{"base_url" : "http://services.org/restapi/","methods" : { "get_info" : { "path" : "/show", "method" : "GET" } } }'
-);
+ok $client = Net::HTTP::Spore->new_from_string(JSON::encode_json($api_ok));
+ok $client->meta->_find_spore_method_by_name(sub{/^get_info$/});
+
+dies_ok {
+ Net::HTTP::Spore->new_from_strings('/a/b/c', '/a/b/c');
+};
+
+for ( {}, { base_url => 'http://localhost/api' } ) {
+ ok $client = Net::HTTP::Spore->new_from_strings( JSON::encode_json($api_ok),
+ JSON::encode_json($second_api), $_ );
+ ok $client->meta->_find_spore_method_by_name( sub { /^get_info$/ } );
+ ok $client->meta->_find_spore_method_by_name( sub { /^list_users$/ } );
+}
+
+dies_ok {
+ $client = Net::HTTP::Spore->new_from_specs($api_spec, $api2_spec);
+};
+like $@, qr/base_url is missing/;
+
+ok $client =
+ Net::HTTP::Spore->new_from_specs( $api_spec, $api2_spec,
+ { base_url => 'http://localhost' } );
+