File Coverage

blib/lib/Net/HTTP/Spore.pm
Criterion Covered Total %
statement 76 85 89.4
branch 16 20 80.0
condition 3 3 100.0
subroutine 18 18 100.0
pod 2 4 50.0
total 115 130 88.4


line stmt bran cond sub pod time code
1             package Net::HTTP::Spore;
2             $Net::HTTP::Spore::VERSION = '0.07';
3             # ABSTRACT: SPORE client
4              
5 21     21   1168569 use Moose;
  21         7204415  
  21         154  
6              
7 21     21   148783 use IO::All;
  21         177605  
  21         197  
8 21     21   8207 use JSON;
  21         111064  
  21         136  
9 21     21   2719 use Carp;
  21         40  
  21         1069  
10 21     21   126 use Try::Tiny;
  21         39  
  21         875  
11 21     21   112 use Scalar::Util;
  21         40  
  21         647  
12              
13 21     21   6371 use Net::HTTP::Spore::Core;
  21         70  
  21         14273  
14              
15             # XXX should we let the possibility to override this super class, or add
16             # another superclasses?
17              
18             sub new_from_string {
19 30     30 1 10124 my ($class, $string, %args) = @_;
20              
21 30         333 my $spore_class =
22             Class::MOP::Class->create_anon_class(
23             superclasses => ['Net::HTTP::Spore::Core'] );
24              
25 30         104330 my $spore_object = $class->_attach_spec_to_class($string, \%args, $spore_class);
26              
27 27         306 return $spore_object;
28             }
29              
30             sub new_from_strings {
31 5     5 0 2062 my $class = shift;
32              
33 5         8 my $opts;
34 5 100       24 if (ref ($_[-1]) eq 'HASH') {
35 3         10 $opts = pop @_;
36             }
37 5         16 my @strings = @_;
38              
39 5         37 my $spore_class =
40             Class::MOP::Class->create_anon_class(
41             superclasses => ['Net::HTTP::Spore::Core'] );
42              
43 5         15005 my $spore_object = undef;
44 5         19 foreach my $string (@strings) {
45 8         41 $spore_object = $class->_attach_spec_to_class($string, $opts, $spore_class, $spore_object);
46             }
47 3         43 return $spore_object;
48             }
49              
50             sub new_from_spec {
51 22     22 1 21268 my ( $class, $spec_file, %args ) = @_;
52              
53 22 100       115 Carp::confess("specification file is missing") unless $spec_file;
54              
55 21         89 my $content = _read_spec($spec_file);
56              
57 20         571 $class->new_from_string( $content, %args );
58             }
59              
60             sub new_from_specs {
61 2     2 0 2114 my $class = shift;
62              
63 2         4 my $opts;
64 2 100       9 if (ref ($_[-1]) eq 'HASH') {
65 1         3 $opts = pop @_;
66             }
67 2         77 my @specs = @_;
68              
69 2         5 my @strings;
70 2         6 foreach my $spec (@specs) {
71 4         45 push @strings,_read_spec($spec);
72             }
73              
74 2         39 $class->new_from_strings(@strings, $opts);
75             }
76              
77             sub _attach_spec_to_class {
78 38     38   175 my ( $class, $string, $opts, $spore_class, $object ) = @_;
79              
80 38         81 my $spec;
81             try {
82 38     38   3642 $spec = JSON::decode_json($string);
83             }
84             catch {
85 1     1   40 Carp::confess( "unable to parse JSON spec: " . $_ );
86 38         399 };
87              
88             try {
89 37   100 37   1752 $opts->{base_url} ||= $spec->{base_url};
90 37 100       202 die "base_url is missing!" if !$opts->{base_url};
91              
92 35 50       139 if ( $spec->{formats} ) {
93 0         0 $opts->{formats} = $spec->{formats};
94             }
95              
96 35 100       145 if ( $spec->{authentication} ) {
97 1         16 $opts->{authentication} = $spec->{authentication};
98             }
99              
100 35 100       161 if ( !$object ) {
101 32         255 $object = $spore_class->new_object(%$opts);
102             }
103 35         419 $object = $class->_add_methods( $object, $spec->{methods} );
104             }
105             catch {
106 4     4   85228 Carp::confess( "unable to create new Net::HTTP::Spore object: " . $_ );
107 37         878 };
108              
109 33         1160 return $object;
110             }
111              
112             sub _read_spec {
113 25     25   63 my $spec_file = shift;
114              
115 25         49 my $content;
116              
117 25 50       112 if ( $spec_file =~ m!^http(s)?://! ) {
118 0         0 my $uri = URI->new($spec_file);
119 0         0 my $req = HTTP::Request->new( GET => $spec_file );
120 0         0 my $ua = LWP::UserAgent->new();
121 0         0 my $res = $ua->request($req);
122 0 0       0 unless( $res->is_success ) {
123 0         0 my $status = $res->status_line;
124 0         0 Carp::confess("Unabled to fetch $spec_file ($status)");
125             }
126 0         0 $content = $res->content;
127             }
128             else {
129 25 100       532 unless ( -f $spec_file ) {
130 1         19 Carp::confess("$spec_file does not exists");
131             }
132 24         149 $content < io($spec_file);
133             }
134              
135 24         111548 return $content;
136             }
137              
138             sub _add_methods {
139 35     35   122 my ($class, $spore, $methods_spec) = @_;
140              
141 35         163 foreach my $method_name (keys %$methods_spec) {
142             $spore->meta->add_spore_method($method_name,
143 122         18555 %{$methods_spec->{$method_name}});
  122         2791  
144             }
145 33         6707 $spore;
146             }
147              
148             1;
149              
150             __END__
151              
152             =pod
153              
154             =encoding UTF-8
155              
156             =head1 NAME
157              
158             Net::HTTP::Spore - SPORE client
159              
160             =head1 VERSION
161              
162             version 0.07
163              
164             =head1 SYNOPSIS
165              
166             my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
167              
168             # from JSON specification string
169             my $client = Net::HTTP::Spore->new_from_string($json);
170              
171             # for identica
172             my $client = Net::HTTP::Spore->new_from_spec('twitter.json', base_url => 'http://identi.ca/com/api');
173              
174             $client->enable('Format::JSON');
175              
176             my $timeline = $client->public_timeline(format => 'json');
177             my $tweets = $timeline->body;
178              
179             foreach my $tweet (@$tweets) {
180             print $tweet->{user}->{screen_name}. " says ".$tweet->{text}."\n";
181             }
182              
183             my $friends_timeline = $client->friends_timeline(format => 'json');
184              
185             =head1 DESCRIPTION
186              
187             This module is an implementation of the SPORE specification.
188              
189             To use this client, you need to use or to write a SPORE specification of an
190             API. A description of the SPORE specification format is available at
191             L<http://github.com/SPORE/specifications/blob/master/spore_description.pod>
192              
193             Some specifications for well-known services are available
194             L<http://github.com/SPORE/api-description>.
195              
196             =head2 CLIENT CREATION
197              
198             First you need to create a client. This can be done using two methods,
199             B<new_from_spec> and B<new_from_string>. The client will read the specification
200             file to create the appropriate methods to interact with the API.
201              
202             =head2 MIDDLEWARES
203              
204             It's possible to activate some middlewares to extend the usage of the client.
205             If you're using an API that discuss in JSON, you can enable the middleware
206             L<Net::HTTP::Spore::Middleware::JSON>.
207              
208             $client->enable('Format::JSON');
209              
210             or only on some path
211              
212             $client->enable_if(sub{$_->[0]->path =~ m!/path/to/json/stuff!}, 'Format::JSON');
213              
214             For very simple middlewares, you can simply pass in an anonymous function
215              
216             $client->enable( sub { my $request = shift; ... } );
217              
218             =head2 METHODS
219              
220             =over 4
221              
222             =item new_from_spec($specification_file, %args)
223              
224             Create and return a L<Net::HTTP::Spore::Core> object, with methods generated
225             from the specification file. The specification file can either be a file on
226             disk or a remote URL.
227              
228             =item new_from_string($specification_string, %args)
229              
230             Create and return a L<Net::HTTP::Spore::Core> object, with methods
231             generated from a JSON specification string.
232              
233             =back
234              
235             =head2 TRACING
236              
237             L<Net::HTTP::Spore> provides a way to trace what's going on when doing a
238             request.
239              
240             =head3 Enabling Trace
241              
242             You can enable tracing using the environment variable B<SPORE_TRACE>. You can
243             also enable tracing at construct time by adding B<trace =E<gt> 1> when calling
244             B<new_from_spec>.
245              
246             =head3 Trace Output
247              
248             By default output will be directed to B<STDERR>. You can specify another
249             default output:
250              
251             SPORE_TRACE=1=log.txt
252              
253             or
254              
255             ->new_from_spec('spec.json', trace => '1=log.txt');
256              
257             =head1 AUTHORS
258              
259             =over 4
260              
261             =item *
262              
263             Franck Cuny <franck.cuny@gmail.com>
264              
265             =item *
266              
267             Ash Berlin <ash@cpan.org>
268              
269             =item *
270              
271             Ahmad Fatoum <athreef@cpan.org>
272              
273             =back
274              
275             =head1 COPYRIGHT AND LICENSE
276              
277             This software is copyright (c) 2012 by Linkfluence.
278              
279             This is free software; you can redistribute it and/or modify it under
280             the same terms as the Perl 5 programming language system itself.
281              
282             =cut