File Coverage

blib/lib/MetaCPAN/API/Tiny.pm
Criterion Covered Total %
statement 113 127 88.9
branch 57 72 79.1
condition 20 37 54.0
subroutine 15 16 93.7
pod 9 9 100.0
total 214 261 81.9


line stmt bran cond sub pod time code
1             package MetaCPAN::API::Tiny;
2             $MetaCPAN::API::Tiny::VERSION = '1.150270';
3 10     10   192215 use strict;
  10         21  
  10         369  
4 10     10   45 use warnings;
  10         15  
  10         251  
5             # ABSTRACT: (DEPRECATED) A Tiny API client for MetaCPAN
6              
7 10     10   44 use Carp;
  10         18  
  10         928  
8 10     10   10833 use JSON::PP 'encode_json', 'decode_json';
  10         180866  
  10         964  
9 10     10   7979 use HTTP::Tiny;
  10         484757  
  10         15384  
10              
11              
12             sub new {
13 11     11 1 1040 my ($class, @args) = @_;
14              
15 11 50       74 $#_ % 2 == 0
16             or croak 'Arguments must be provided as name/value pairs';
17            
18 11         46 my %params = @args;
19              
20 11 50 66     121 die 'ua_args must be an array reference'
21             if $params{ua_args} && ref($params{ua_args}) ne 'ARRAY';
22              
23             my $self = +{
24             base_url => $params{base_url} || 'http://api.metacpan.org/v0',
25             ua => $params{ua} || HTTP::Tiny->new(
26             $params{ua_args}
27 11   50     142 ? @{$params{ua_args}}
      33        
28             : (agent => 'MetaCPAN::API::Tiny/'
29             . ($MetaCPAN::API::VERSION || 'xx'))),
30             };
31            
32 11         1031 return bless($self, $class);
33             }
34              
35             sub _build_extra_params {
36 10     10   1616 my $self = shift;
37              
38 10 100       182 @_ % 2 == 0
39             or croak 'Incorrect number of params, must be key/value';
40              
41 9         21 my %extra = @_;
42 9         35 my $ua = $self->{ua};
43              
44 9         31 foreach my $key (keys %extra)
45             {
46             # The implementation in HTTP::Tiny uses + instead of %20, fix that
47 5         19 $extra{$key} = $ua->_uri_escape($extra{$key});
48 5         114 $extra{$key} =~ s/\+/%20/g;
49             }
50              
51 9         36 my $params = join '&', map { "$_=" . $extra{$_} } sort keys %extra;
  5         15  
52              
53 9         28 return $params;
54             }
55              
56              
57             # /source/{author}/{release}/{path}
58             sub source {
59 3     3 1 2073 my $self = shift;
60 3 100       38 my %opts = @_ ? @_ : ();
61 3         4 my $url = '';
62 3         4 my $error = "Provide 'author' and 'release' and 'path'";
63              
64 3 100       153 %opts or croak $error;
65              
66 2 100 66     15 if (
      66        
67             defined ( my $author = $opts{'author'} ) &&
68             defined ( my $release = $opts{'release'} ) &&
69             defined ( my $path = $opts{'path'} )
70             ) {
71 1         4 $url = "source/$author/$release/$path";
72             } else {
73 1         75 croak $error;
74             }
75              
76 1         7 $url = $self->{base_url} . "/$url";
77            
78 1         35 my $result = $self->{ua}->get($url);
79 1 50       41523 $result->{'success'}
80             or croak "Failed to fetch '$url': " . $result->{'reason'};
81              
82 1         41 return $result->{'content'};
83             }
84              
85              
86             # /release/{distribution}
87             # /release/{author}/{release}
88             sub release {
89 4     4 1 2011 my $self = shift;
90 4 100       20 my %opts = @_ ? @_ : ();
91 4         4 my $url = '';
92 4         8 my $error = "Either provide 'distribution', or 'author' and 'release', " .
93             "or 'search'";
94              
95 4 100       161 %opts or croak $error;
96              
97 3         5 my %extra_opts = ();
98              
99 3 100 66     22 if ( defined ( my $dist = $opts{'distribution'} ) ) {
    100          
    50          
100 1         3 $url = "release/$dist";
101             } elsif (
102             defined ( my $author = $opts{'author'} ) &&
103             defined ( my $release = $opts{'release'} )
104             ) {
105 1         5 $url = "release/$author/$release";
106             } elsif ( defined ( my $search_opts = $opts{'search'} ) ) {
107 0 0 0     0 ref $search_opts && ref $search_opts eq 'HASH'
108             or croak $error;
109              
110 0         0 %extra_opts = %{$search_opts};
  0         0  
111 0         0 $url = 'release/_search';
112             } else {
113 1         73 croak $error;
114             }
115              
116 2         9 return $self->fetch( $url, %extra_opts );
117             }
118              
119              
120             # /pod/{module}
121             # /pod/{author}/{release}/{path}
122             sub pod {
123 9     9 1 8054 my $self = shift;
124 9 100       56 my %opts = @_ ? @_ : ();
125 9         17 my $url = '';
126 9         19 my $error = "Either provide 'module' or 'author and 'release' and 'path'";
127              
128 9 100       236 %opts or croak $error;
129              
130 8 100 66     43 if ( defined ( my $module = $opts{'module'} ) ) {
    100 66        
131 6         18 $url = "pod/$module";
132             } elsif (
133             defined ( my $author = $opts{'author'} ) &&
134             defined ( my $release = $opts{'release'} ) &&
135             defined ( my $path = $opts{'path'} )
136             ) {
137 1         3 $url = "pod/$author/$release/$path";
138             } else {
139 1         110 croak $error;
140             }
141              
142             # check content-type
143 7         15 my %extra = ();
144 7 100       32 if ( defined ( my $type = $opts{'content-type'} ) ) {
145 5 100       253 $type =~ m{^ text/ (?: html|plain|x-pod|x-markdown ) $}x
146             or croak 'Incorrect content-type provided';
147              
148 4         14 $extra{headers}{'content-type'} = $type;
149             }
150              
151 6         30 $url = $self->{base_url}. "/$url";
152            
153 6         239 my $result = $self->{ua}->get( $url, \%extra );
154 6 50       679688 $result->{'success'}
155             or croak "Failed to fetch '$url': " . $result->{'reason'};
156              
157 6         120 return $result->{'content'};
158             }
159              
160              
161             # /module/{module}
162             sub module {
163 2     2 1 1688 my $self = shift;
164 2         2 my $name = shift;
165              
166 2 100       153 $name or croak 'Please provide a module name';
167              
168 1         6 return $self->fetch("module/$name");
169             }
170              
171              
172             # file() is a synonym of module
173 0     0 1 0 sub file { goto &module }
174              
175              
176             # /author/{author}
177             sub author {
178 2     2 1 1856 my $self = shift;
179 2         3 my ( $pause_id, $url, %extra_opts );
180              
181 2 100       7 if ( @_ == 1 ) {
    50          
182 1         3 $url = 'author/' . shift;
183             } elsif ( @_ == 2 ) {
184 0         0 my %opts = @_;
185              
186 0 0       0 if ( defined $opts{'pauseid'} ) {
    0          
187 0         0 $url = "author/" . $opts{'pauseid'};
188             } elsif ( defined $opts{'search'} ) {
189 0         0 my $search_opts = $opts{'search'};
190              
191 0 0 0     0 ref $search_opts && ref $search_opts eq 'HASH'
192             or croak "'search' key must be hashref";
193              
194 0         0 %extra_opts = %{$search_opts};
  0         0  
195 0         0 $url = 'author/_search';
196             } else {
197 0         0 croak 'Unknown option given';
198             }
199             } else {
200 1         157 croak 'Please provide an author PAUSEID or a "search"';
201             }
202              
203 1         6 return $self->fetch( $url, %extra_opts );
204             }
205              
206              
207              
208             sub fetch {
209 6     6 1 1663 my $self = shift;
210 6         13 my $url = shift;
211 6         23 my $extra = $self->_build_extra_params(@_);
212 6         13 my $base = $self->{base_url};
213 6 100       27 my $req_url = $extra ? "$base/$url?$extra" : "$base/$url";
214            
215 6         148 my $result = $self->{ua}->get($req_url);
216 6         1724825 return $self->_decode_result( $result, $req_url );
217             }
218              
219              
220             sub post {
221 5     5 1 2826 my $self = shift;
222 5         6 my $url = shift;
223 5         4 my $query = shift;
224 5         10 my $base = $self->{base_url};
225              
226 5 100       145 defined $url
227             or croak 'First argument of URL must be provided';
228              
229 4 100 66     153 ref $query and ref $query eq 'HASH'
230             or croak 'Second argument of query hashref must be provided';
231              
232 2         6 my $query_json = encode_json( $query );
233 2         182 my $result = $self->{ua}->request(
234             'POST',
235             "$base/$url",
236             {
237             headers => { 'Content-Type' => 'application/json' },
238             content => $query_json,
239             }
240             );
241              
242 2         3677 return $self->_decode_result( $result, $url, $query_json );
243             }
244              
245             sub _decode_result {
246 15     15   3120 my $self = shift;
247 15         30 my ( $result, $url, $original ) = @_;
248 15         20 my $decoded_result;
249              
250 15 100 66     250 ref $result and ref $result eq 'HASH'
251             or croak 'First argument must be hashref';
252              
253 14 100       109 defined $url
254             or croak 'Second argument of a URL must be provided';
255              
256 13 100       50 if ( defined ( my $success = $result->{'success'} ) ) {
257 12   100     53 my $reason = $result->{'reason'} || '';
258 12 100       41 $reason .= ( defined $original ? " (request: $original)" : '' );
259              
260 12 100       178 $success or croak "Failed to fetch '$url': $reason";
261             } else {
262 1         82 croak 'Missing success in return value';
263             }
264              
265 10 50       207 defined ( my $content = $result->{'content'} )
266             or croak 'Missing content in return value';
267              
268 10         51 eval { $decoded_result = decode_json $content; 1 }
  9         978367  
269 10 100       47 or do { croak "Couldn't decode '$content': $@" };
  1         289  
270              
271 9         110 return $decoded_result;
272             }
273              
274             1;
275              
276             __END__