File Coverage

blib/lib/MetaCPAN/Client.pm
Criterion Covered Total %
statement 178 238 74.7
branch 30 56 53.5
condition 20 38 52.6
subroutine 41 49 83.6
pod 19 19 100.0
total 288 400 72.0


line stmt bran cond sub pod time code
1 19     19   2111078 use strict;
  19         35  
  19         558  
2 19     19   80 use warnings;
  19         31  
  19         1239  
3             package MetaCPAN::Client;
4             # ABSTRACT: A comprehensive, DWIM-featured client to the MetaCPAN API
5             $MetaCPAN::Client::VERSION = '2.042000';
6 19     19   8411 use Moo;
  19         111981  
  19         94  
7 19     19   23706 use Carp;
  19         44  
  19         1235  
8 19     19   7472 use JSON::MaybeXS qw< JSON >;
  19         257646  
  19         1480  
9 19     19   5567 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  19         25763  
  19         1516  
10 19     19   7650 use URI::Escape qw< uri_escape_utf8 >;
  19         30631  
  19         1302  
11              
12 19     19   8662 use MetaCPAN::Client::Request;
  19         73  
  19         748  
13 19     19   9766 use MetaCPAN::Client::Author;
  19         88  
  19         718  
14 19     19   9927 use MetaCPAN::Client::Distribution;
  19         64  
  19         619  
15 19     19   8007 use MetaCPAN::Client::DownloadURL;
  19         68  
  19         783  
16 19     19   8367 use MetaCPAN::Client::Module;
  19         84  
  19         719  
17 19     19   179 use MetaCPAN::Client::File;
  19         32  
  19         382  
18 19     19   11034 use MetaCPAN::Client::Favorite;
  19         61  
  19         644  
19 19     19   8062 use MetaCPAN::Client::Pod;
  19         59  
  19         658  
20 19     19   10975 use MetaCPAN::Client::Release;
  19         92  
  19         732  
21 19     19   9878 use MetaCPAN::Client::Mirror;
  19         78  
  19         729  
22 19     19   8901 use MetaCPAN::Client::Package;
  19         62  
  19         598  
23 19     19   7737 use MetaCPAN::Client::Permission;
  19         68  
  19         629  
24 19     19   8486 use MetaCPAN::Client::ResultSet;
  19         58  
  19         641  
25 19     19   7959 use MetaCPAN::Client::Cover;
  19         64  
  19         628  
26 19     19   7661 use MetaCPAN::Client::Cve;
  19         70  
  19         39149  
27              
28             has request => (
29             is => 'ro',
30             handles => [qw],
31             );
32              
33             my @supported_searches = qw<
34             author distribution favorite module release mirror file permission package cover cve
35             >;
36              
37             sub BUILDARGS {
38 19     19 1 5336736 my ( $class, %args ) = @_;
39              
40             $args{'request'} ||= MetaCPAN::Client::Request->new(
41             ( ua => $args{ua} )x!! $args{ua},
42             ( ua_args => $args{ua_args} )x!! $args{ua_args},
43             ( domain => $args{domain} )x!! $args{domain},
44             ( debug => $args{debug} )x!! $args{debug},
45 19   33     490 );
46              
47 19         1104 return \%args;
48             }
49              
50             sub author {
51 7     7 1 239473 my $self = shift;
52 7         21 my $arg = shift;
53 7         20 my $params = shift;
54              
55 7         35 return $self->_get_or_search( 'author', $arg, $params );
56             }
57              
58             sub module {
59 2     2 1 1089 my $self = shift;
60 2         5 my $arg = shift;
61 2         5 my $params = shift;
62              
63 2         8 return $self->_get_or_search( 'module', $arg, $params );
64             }
65              
66             sub distribution {
67 31     31 1 32425 my $self = shift;
68 31         100 my $arg = shift;
69 31         78 my $params = shift;
70              
71 31         122 return $self->_get_or_search( 'distribution', $arg, $params );
72             }
73              
74             sub file {
75 1     1 1 1365 my $self = shift;
76 1         3 my $arg = shift;
77 1         2 my $params = shift;
78              
79 1         6 return $self->_get_or_search( 'file', $arg, $params );
80             }
81              
82             sub package {
83 1     1 1 766 my $self = shift;
84 1         1 my $arg = shift;
85 1         2 my $params = shift;
86              
87 1         4 return $self->_get_or_search( 'package', $arg, $params );
88             }
89              
90             sub permission {
91 1     1 1 770 my $self = shift;
92 1         2 my $arg = shift;
93 1         2 my $params = shift;
94              
95 1         3 return $self->_get_or_search( 'permission', $arg, $params );
96             }
97              
98             sub cover {
99 1     1 1 1235 my $self = shift;
100 1         2 my $arg = shift;
101 1         3 my $params = shift;
102              
103 1         6 return $self->_get_or_search( 'cover', $arg, $params );
104             }
105              
106             sub cve {
107 1     1 1 757 my $self = shift;
108 1         2 my $arg = shift;
109 1         2 my $params = shift;
110              
111 1         4 return $self->_get_or_search( 'cve', $arg, $params );
112             }
113              
114             sub pod {
115 1     1 1 811 my $self = shift;
116 1         1 my $name = shift;
117 1   50     5 my $params = shift || {};
118              
119 1         12 return MetaCPAN::Client::Pod->new({
120             request => $self->request,
121             name => $name,
122             %$params
123             });
124             }
125              
126             sub favorite {
127 2     2 1 73909 my $self = shift;
128 2         5 my $args = shift;
129 2         28 my $params = shift;
130              
131 2 50       14 is_hashref($args)
132             or croak 'favorite takes a hash ref as parameter';
133              
134 2         41 return $self->_search( 'favorite', $args, $params );
135             }
136              
137             sub release {
138 2     2 1 859 my $self = shift;
139 2         5 my $arg = shift;
140 2         4 my $params = shift;
141              
142 2         7 return $self->_get_or_search( 'release', $arg, $params );
143             }
144              
145             sub mirror {
146 0     0 1 0 my $self = shift;
147 0         0 my $arg = shift;
148 0         0 my $params = shift;
149              
150 0         0 return $self->_get_or_search( 'mirror', $arg, $params );
151             }
152              
153             sub reverse_dependencies {
154 1     1 1 1416 my $self = shift;
155 1         2 my $dist = shift;
156              
157 1         5 $dist =~ s/::/-/g;
158              
159 1         4 return $self->_reverse_deps($dist);
160             }
161              
162             *rev_deps = *reverse_dependencies;
163              
164             sub recent {
165 0     0 1 0 my $self = shift;
166 0   0     0 my $size = shift || 100;
167              
168 0 0       0 $size eq 'today'
169             and return $self->_recent(
170             size => 1000,
171             query => {
172             bool => {
173             filter => _filter_today()
174             }
175             }
176             );
177              
178 0 0       0 $size =~ /^[0-9]+$/
179             and return $self->_recent( size => $size );
180              
181 0         0 croak "recent: invalid size value";
182             }
183              
184             sub all {
185 0     0 1 0 my $self = shift;
186 0         0 my $type = shift;
187 0         0 my $params = shift;
188              
189             # This endpoint used to support only pluralized types (mostly) and convert
190             # to singular types before redispatching. Now it accepts both plural and
191             # unplural forms directly and relies on the underlying methods it
192             # dispatches to to check types (using the global supported types array).
193 0         0 $type =~ s/s$//;
194              
195 0 0 0     0 if ( $params and !is_hashref($params) ) {
196 0         0 croak "all: params must be a hashref";
197             }
198              
199 0 0 0     0 if ( exists $params->{fields} and !is_arrayref($params->{fields}) ) {
200 0         0 $params->{fields} = [ split /,/ => $params->{fields} ];
201             }
202              
203 0         0 return $self->$type( { __MATCH_ALL__ => 1 }, $params );
204             }
205              
206             sub download_url {
207 4     4 1 6265 my $self = shift;
208 4         8 my $module = shift;
209 4         8 my $version_or_range = shift;
210 4         6 my $dev = shift;
211              
212 4         7 my $uri = $module;
213 4         7 my @extra;
214 4 100       13 if ( defined $version_or_range ) {
215              
216 3         14 my @valid_ranges = qw{ == != <= >= < > ! };
217 3         5 my $is_using_range;
218 3         15 foreach my $range ( @valid_ranges ) {
219 15 100       34 if ( index( $version_or_range, $range ) >= 0 ) {
220 2         4 $is_using_range = 1;
221 2         4 last;
222             }
223             }
224             # by default use the '==' operator when no range set
225 3 100       10 $version_or_range = '==' . $version_or_range unless $is_using_range;
226              
227             # version=>0.21,<0.27,!=0.26&dev=1
228 3         22 push @extra, 'version=' .uri_escape_utf8($version_or_range);
229             }
230 4 100       184 if ( defined $dev ) {
231 1         3 push @extra, 'dev=' . uri_escape_utf8($dev);
232             }
233              
234 4 100       30 $uri .= '?'.join('&', @extra) if scalar @extra;
235              
236 4         13 return $self->_get( 'download_url', $uri );
237             }
238              
239             sub autocomplete {
240 0     0 1 0 my $self = shift;
241 0         0 my $q = shift;
242              
243 0         0 my $res;
244              
245             eval {
246 0         0 $res = $self->fetch( '/search/autocomplete?q=' . uri_escape_utf8($q) );
247 0         0 1;
248              
249 0 0       0 } or do {
250 0         0 warn $@;
251 0         0 return [];
252             };
253              
254             return [
255 0         0 map { $_->{fields} } @{ $res->{hits}{hits} }
  0         0  
  0         0  
256             ];
257             }
258              
259             sub autocomplete_suggest {
260 0     0 1 0 my $self = shift;
261 0         0 my $q = shift;
262              
263 0         0 my $res;
264              
265             eval {
266 0         0 $res = $self->fetch( '/search/autocomplete/suggest?q=' . uri_escape_utf8($q) );
267 0         0 1;
268              
269 0 0       0 } or do {
270 0         0 warn $@;
271 0         0 return [];
272             };
273              
274 0         0 return $res->{suggestions};
275             }
276              
277             ###
278              
279             sub _get {
280 49     49   3840 my $self = shift;
281              
282 49 100 66     1345 ( scalar(@_) == 2
      66        
      66        
283             or ( scalar(@_) == 3 and ( !defined $_[2] or is_hashref($_[2]) ) ) )
284             or croak '_get takes type and search string as parameters (and an optional params hash)';
285              
286 46         94 my $type = shift;
287 46         73 my $arg = shift;
288 46         103 my $params = shift;
289              
290 46         280 my $fields_filter = $self->_read_fields( $params );
291              
292 46   50     1544 my $response = $self->fetch(
293             sprintf("%s/%s%s", $type ,$arg, $fields_filter||'')
294             );
295 46 100       2834 is_hashref($response)
296             or croak sprintf( 'Failed to fetch %s (%s)', ucfirst($type), $arg );
297              
298 45 100       194 $type = 'DownloadURL' if $type eq 'download_url';
299              
300             # deal with API response inconsistency
301 45 50 66     209 if ( $type eq 'cve' and is_hashref($response) and is_arrayref($response->{cve} ) ) {
      66        
302 1         5 $response = $response->{cve}[0];
303             }
304              
305 45         188 my $class = 'MetaCPAN::Client::' . ucfirst($type);
306 45         580 return $class->new_from_request($response, $self);
307             }
308              
309             sub _read_fields {
310 46     46   98 my $self = shift;
311 46         78 my $params = shift;
312 46 50       164 $params or return;
313              
314 0         0 my $fields = delete $params->{fields};
315 0 0       0 $fields or return;
316              
317 0 0       0 if ( is_arrayref($fields) ) {
    0          
318 0 0       0 grep { ref $_ } @$fields
  0         0  
319             and croak "fields array should not contain any refs.";
320              
321 0         0 return sprintf( "?fields=%s", join q{,} => @$fields );
322              
323             } elsif ( !ref $fields ) {
324              
325 0         0 return "?fields=$fields";
326             }
327              
328 0         0 croak "invalid param: fields";
329             }
330              
331             sub _search {
332 14     14   5676 my $self = shift;
333 14         34 my $type = shift;
334 14         41 my $args = shift;
335 14         28 my $params = shift;
336              
337 14 100       585 is_hashref($args)
338             or croak '_search takes a hash ref as query';
339              
340 13 100 100     147 ! defined $params or is_hashref($params)
341             or croak '_search takes a hash ref as query parameters';
342              
343 12   100     84 $params ||= {};
344              
345 12 100       91 grep { $_ eq $type } @supported_searches
  132         480  
346             or croak 'search type is not supported';
347              
348 11         393 my $scroller = $self->ssearch($type, $args, $params);
349              
350 11         5474 return MetaCPAN::Client::ResultSet->new(
351             scroller => $scroller,
352             type => $type,
353             );
354             }
355              
356             sub _get_or_search {
357 50     50   10792 my $self = shift;
358 50         104 my $type = shift;
359 50         106 my $arg = shift;
360 50         96 my $params = shift;
361              
362 50 100       280 is_hashref($arg) and
363             return $self->_search( $type, $arg, $params );
364              
365 42 100 66     379 defined $arg and !is_ref($arg)
366             and return $self->_get($type, $arg, $params);
367              
368 1         327 croak "$type: invalid args (takes scalar value or search parameters hashref)";
369             }
370              
371             sub _reverse_deps {
372 1     1   3 my $self = shift;
373 1         2 my $dist = shift;
374              
375 1         1 my $res;
376              
377             eval {
378 1         8 $res = $self->fetch(
379             "/reverse_dependencies/dist/$dist",
380             {
381             size => 5000,
382             query => {
383             bool => {
384             must => [
385             { term => { 'status' => 'latest' } },
386             { term => { 'authorized' => JSON->true } },
387             ]
388             }
389             },
390             }
391             );
392 1         15 1;
393              
394 1 50       3 } or do {
395 0         0 warn $@;
396 0         0 return _empty_result_set('release'),
397             };
398              
399             return MetaCPAN::Client::ResultSet->new(
400 1         24 items => $res->{'data'},
401             type => 'release',
402             );
403             }
404              
405             sub _recent {
406 0     0     my $self = shift;
407 0           my @args = @_;
408              
409 0           my $res;
410              
411             eval {
412 0           $res = $self->fetch(
413             '/release/_search',
414             {
415             from => 0,
416             query => { match_all => {} },
417             @args,
418             sort => [ { 'date' => { order => "desc" } } ],
419             }
420             );
421 0           1;
422              
423 0 0         } or do {
424 0           warn $@;
425 0           return _empty_result_set('release');
426             };
427              
428             return MetaCPAN::Client::ResultSet->new(
429 0           items => $res->{'hits'}{'hits'},
430             type => 'release',
431             );
432             }
433              
434             sub _filter_today {
435 0     0     return { range => { date => { from => "now/1d+0h" } } };
436             }
437              
438             sub _empty_result_set {
439 0     0     my $type = shift;
440              
441 0           return MetaCPAN::Client::ResultSet->new(
442             items => [],
443             type => $type,
444             );
445             }
446              
447             1;
448              
449             __END__