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.08';
3             # ABSTRACT: SPORE client
4              
5 22     22   1506896 use Moose;
  22         8081543  
  22         153  
6              
7 22     22   164389 use IO::All;
  22         198269  
  22         199  
8 22     22   8914 use JSON;
  22         121970  
  22         140  
9 22     22   2874 use Carp;
  22         41  
  22         1156  
10 22     22   133 use Try::Tiny;
  22         44  
  22         963  
11 22     22   133 use Scalar::Util;
  22         37  
  22         690  
12              
13 22     22   6965 use Net::HTTP::Spore::Core;
  22         70  
  22         14626  
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 9998 my ($class, $string, %args) = @_;
20              
21 31         354 my $spore_class =
22             Class::MOP::Class->create_anon_class(
23             superclasses => ['Net::HTTP::Spore::Core'] );
24              
25 31         108087 my $spore_object = $class->_attach_spec_to_class($string, \%args, $spore_class);
26              
27 28         326 return $spore_object;
28             }
29              
30             sub new_from_strings {
31 5     5 0 1963 my $class = shift;
32              
33 5         8 my $opts;
34 5 100       24 if (ref ($_[-1]) eq 'HASH') {
35 3         9 $opts = pop @_;
36             }
37 5         14 my @strings = @_;
38              
39 5         35 my $spore_class =
40             Class::MOP::Class->create_anon_class(
41             superclasses => ['Net::HTTP::Spore::Core'] );
42              
43 5         14479 my $spore_object = undef;
44 5         14 foreach my $string (@strings) {
45 8         33 $spore_object = $class->_attach_spec_to_class($string, $opts, $spore_class, $spore_object);
46             }
47 3         37 return $spore_object;
48             }
49              
50             sub new_from_spec {
51 23     23 1 24276 my ( $class, $spec_file, %args ) = @_;
52              
53 23 100       118 Carp::confess("specification file is missing") unless $spec_file;
54              
55 22         108 my $content = _read_spec($spec_file);
56              
57 21         565 $class->new_from_string( $content, %args );
58             }
59              
60             sub new_from_specs {
61 2     2 0 1951 my $class = shift;
62              
63 2         4 my $opts;
64 2 100       8 if (ref ($_[-1]) eq 'HASH') {
65 1         3 $opts = pop @_;
66             }
67 2         6 my @specs = @_;
68              
69 2         3 my @strings;
70 2         6 foreach my $spec (@specs) {
71 4         70 push @strings,_read_spec($spec);
72             }
73              
74 2         58 $class->new_from_strings(@strings, $opts);
75             }
76              
77             sub _attach_spec_to_class {
78 39     39   176 my ( $class, $string, $opts, $spore_class, $object ) = @_;
79              
80 39         80 my $spec;
81             try {
82 39     39   3727 $spec = JSON::decode_json($string);
83             }
84             catch {
85 1     1   36 Carp::confess( "unable to parse JSON spec: " . $_ );
86 39         411 };
87              
88             try {
89 38   100 38   1909 $opts->{base_url} ||= $spec->{base_url};
90 38 100       179 die "base_url is missing!" if !$opts->{base_url};
91              
92 36 50       149 if ( $spec->{formats} ) {
93 0         0 $opts->{formats} = $spec->{formats};
94             }
95              
96 36 100       123 if ( $spec->{authentication} ) {
97 1         11 $opts->{authentication} = $spec->{authentication};
98             }
99              
100 36 100       205 if ( !$object ) {
101 33         293 $object = $spore_class->new_object(%$opts);
102             }
103 36         467 $object = $class->_add_methods( $object, $spec->{methods} );
104             }
105             catch {
106 4     4   83656 Carp::confess( "unable to create new Net::HTTP::Spore object: " . $_ );
107 38         907 };
108              
109 34         1191 return $object;
110             }
111              
112             sub _read_spec {
113 26     26   70 my $spec_file = shift;
114              
115 26         63 my $content;
116              
117 26 50       114 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       573 unless ( -f $spec_file ) {
130 1         24 Carp::confess("$spec_file does not exists");
131             }
132 25         168 $content < io($spec_file);
133             }
134              
135 25         118043 return $content;
136             }
137              
138             sub _add_methods {
139 36     36   120 my ($class, $spore, $methods_spec) = @_;
140              
141 36         169 foreach my $method_name (keys %$methods_spec) {
142             $spore->meta->add_spore_method($method_name,
143 127         19707 %{$methods_spec->{$method_name}});
  127         2918  
144             }
145 34         7001 $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.08
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