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.09';
3             # ABSTRACT: SPORE client
4              
5 22     22   1286368 use Moose;
  22         7825322  
  22         161  
6              
7 22     22   155115 use IO::All;
  22         181157  
  22         196  
8 22     22   8242 use JSON;
  22         111823  
  22         144  
9 22     22   3006 use Carp;
  22         50  
  22         1225  
10 22     22   136 use Try::Tiny;
  22         45  
  22         961  
11 22     22   124 use Scalar::Util;
  22         45  
  22         736  
12              
13 22     22   6831 use Net::HTTP::Spore::Core;
  22         72  
  22         15524  
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 31     31 1 9617 my ($class, $string, %args) = @_;
20              
21 31         342 my $spore_class =
22             Class::MOP::Class->create_anon_class(
23             superclasses => ['Net::HTTP::Spore::Core'] );
24              
25 31         106105 my $spore_object = $class->_attach_spec_to_class($string, \%args, $spore_class);
26              
27 28         323 return $spore_object;
28             }
29              
30             sub new_from_strings {
31 5     5 0 1719 my $class = shift;
32              
33 5         9 my $opts;
34 5 100       20 if (ref ($_[-1]) eq 'HASH') {
35 3         7 $opts = pop @_;
36             }
37 5         12 my @strings = @_;
38              
39 5         27 my $spore_class =
40             Class::MOP::Class->create_anon_class(
41             superclasses => ['Net::HTTP::Spore::Core'] );
42              
43 5         14009 my $spore_object = undef;
44 5         12 foreach my $string (@strings) {
45 8         23 $spore_object = $class->_attach_spec_to_class($string, $opts, $spore_class, $spore_object);
46             }
47 3         31 return $spore_object;
48             }
49              
50             sub new_from_spec {
51 23     23 1 19172 my ( $class, $spec_file, %args ) = @_;
52              
53 23 100       105 Carp::confess("specification file is missing") unless $spec_file;
54              
55 22         109 my $content = _read_spec($spec_file);
56              
57 21         575 $class->new_from_string( $content, %args );
58             }
59              
60             sub new_from_specs {
61 2     2 0 1856 my $class = shift;
62              
63 2         5 my $opts;
64 2 100       7 if (ref ($_[-1]) eq 'HASH') {
65 1         2 $opts = pop @_;
66             }
67 2         5 my @specs = @_;
68              
69 2         4 my @strings;
70 2         3 foreach my $spec (@specs) {
71 4         66 push @strings,_read_spec($spec);
72             }
73              
74 2         35 $class->new_from_strings(@strings, $opts);
75             }
76              
77             sub _attach_spec_to_class {
78 39     39   161 my ( $class, $string, $opts, $spore_class, $object ) = @_;
79              
80 39         83 my $spec;
81             try {
82 39     39   3390 $spec = JSON::decode_json($string);
83             }
84             catch {
85 1     1   31 Carp::confess( "unable to parse JSON spec: " . $_ );
86 39         350 };
87              
88             try {
89 38   100 38   1782 $opts->{base_url} ||= $spec->{base_url};
90 38 100       163 die "base_url is missing!" if !$opts->{base_url};
91              
92 36 50       131 if ( $spec->{formats} ) {
93 0         0 $opts->{formats} = $spec->{formats};
94             }
95              
96 36 100       128 if ( $spec->{authentication} ) {
97 1         9 $opts->{authentication} = $spec->{authentication};
98             }
99              
100 36 100       167 if ( !$object ) {
101 33         258 $object = $spore_class->new_object(%$opts);
102             }
103 36         469 $object = $class->_add_methods( $object, $spec->{methods} );
104             }
105             catch {
106 4     4   83945 Carp::confess( "unable to create new Net::HTTP::Spore object: " . $_ );
107 38         838 };
108              
109 34         1116 return $object;
110             }
111              
112             sub _read_spec {
113 26     26   59 my $spec_file = shift;
114              
115 26         51 my $content;
116              
117 26 50       116 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 26 100       498 unless ( -f $spec_file ) {
130 1         18 Carp::confess("$spec_file does not exists");
131             }
132 25         158 $content < io($spec_file);
133             }
134              
135 25         130429 return $content;
136             }
137              
138             sub _add_methods {
139 36     36   124 my ($class, $spore, $methods_spec) = @_;
140              
141 36         167 foreach my $method_name (keys %$methods_spec) {
142             $spore->meta->add_spore_method($method_name,
143 127         19225 %{$methods_spec->{$method_name}});
  127         2854  
144             }
145 34         6732 $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.09
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