File Coverage

blib/lib/MetaCPAN/Client.pm
Criterion Covered Total %
statement 185 247 74.9
branch 31 58 53.4
condition 20 38 52.6
subroutine 42 50 84.0
pod 20 20 100.0
total 298 413 72.1


line stmt bran cond sub pod time code
1 20     20   2192100 use strict;
  20         33  
  20         583  
2 20     20   77 use warnings;
  20         38  
  20         1323  
3             package MetaCPAN::Client;
4             # ABSTRACT: A comprehensive, DWIM-featured client to the MetaCPAN API
5             $MetaCPAN::Client::VERSION = '2.043000';
6 20     20   9292 use Moo;
  20         120064  
  20         111  
7 20     20   24878 use Carp;
  20         44  
  20         1231  
8 20     20   7747 use JSON::MaybeXS qw< JSON >;
  20         262664  
  20         1391  
9 20     20   5773 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  20         27569  
  20         1423  
10 20     20   7263 use URI::Escape qw< uri_escape_utf8 >;
  20         28556  
  20         1274  
11              
12 20     20   8419 use MetaCPAN::Client::Request;
  20         80  
  20         950  
13 20     20   9933 use MetaCPAN::Client::Author;
  20         92  
  20         735  
14 20     20   9489 use MetaCPAN::Client::Distribution;
  20         77  
  20         624  
15 20     20   8019 use MetaCPAN::Client::DownloadURL;
  20         70  
  20         698  
16 20     20   8045 use MetaCPAN::Client::Module;
  20         126  
  20         721  
17 20     20   140 use MetaCPAN::Client::File;
  20         34  
  20         405  
18 20     20   8652 use MetaCPAN::Client::Favorite;
  20         60  
  20         620  
19 20     20   7821 use MetaCPAN::Client::Pod;
  20         66  
  20         666  
20 20     20   8502 use MetaCPAN::Client::Release;
  20         95  
  20         741  
21 20     20   10417 use MetaCPAN::Client::Mirror;
  20         125  
  20         776  
22 20     20   9581 use MetaCPAN::Client::Package;
  20         73  
  20         637  
23 20     20   8812 use MetaCPAN::Client::Permission;
  20         72  
  20         628  
24 20     20   8221 use MetaCPAN::Client::ResultSet;
  20         68  
  20         727  
25 20     20   8269 use MetaCPAN::Client::Cover;
  20         69  
  20         638  
26 20     20   8302 use MetaCPAN::Client::Cve;
  20         76  
  20         41940  
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 20     20 1 5560834 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 20   33     446 );
46              
47 20         1031 return \%args;
48             }
49              
50             sub author {
51 7     7 1 238280 my $self = shift;
52 7         20 my $arg = shift;
53 7         20 my $params = shift;
54              
55 7         36 return $self->_get_or_search( 'author', $arg, $params );
56             }
57              
58             sub module {
59 2     2 1 1011 my $self = shift;
60 2         6 my $arg = shift;
61 2         3 my $params = shift;
62              
63 2         9 return $self->_get_or_search( 'module', $arg, $params );
64             }
65              
66             sub distribution {
67 31     31 1 28769 my $self = shift;
68 31         81 my $arg = shift;
69 31         53 my $params = shift;
70              
71 31         126 return $self->_get_or_search( 'distribution', $arg, $params );
72             }
73              
74             sub file {
75 1     1 1 806 my $self = shift;
76 1         2 my $arg = shift;
77 1         2 my $params = shift;
78              
79 1         4 return $self->_get_or_search( 'file', $arg, $params );
80             }
81              
82             sub package {
83 1     1 1 788 my $self = shift;
84 1         2 my $arg = shift;
85 1         1 my $params = shift;
86              
87 1         4 return $self->_get_or_search( 'package', $arg, $params );
88             }
89              
90             sub permission {
91 1     1 1 805 my $self = shift;
92 1         3 my $arg = shift;
93 1         2 my $params = shift;
94              
95 1         4 return $self->_get_or_search( 'permission', $arg, $params );
96             }
97              
98             sub cover {
99 1     1 1 1249 my $self = shift;
100 1         3 my $arg = shift;
101 1         2 my $params = shift;
102              
103 1         7 return $self->_get_or_search( 'cover', $arg, $params );
104             }
105              
106             sub cve {
107 1     1 1 1316 my $self = shift;
108 1         3 my $arg = shift;
109 1         2 my $params = shift;
110              
111 1         6 return $self->_get_or_search( 'cve', $arg, $params );
112             }
113              
114             sub pod {
115 1     1 1 1022 my $self = shift;
116 1         4 my $name = shift;
117 1   50     7 my $params = shift || {};
118              
119 1         16 return MetaCPAN::Client::Pod->new({
120             request => $self->request,
121             name => $name,
122             %$params
123             });
124             }
125              
126             sub favorite {
127 2     2 1 83789 my $self = shift;
128 2         5 my $args = shift;
129 2         5 my $params = shift;
130              
131 2 50       19 is_hashref($args)
132             or croak 'favorite takes a hash ref as parameter';
133              
134 2         11 return $self->_search( 'favorite', $args, $params );
135             }
136              
137             sub release {
138 2     2 1 770 my $self = shift;
139 2         4 my $arg = shift;
140 2         4 my $params = shift;
141              
142 2         6 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 830 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 count {
185 2     2 1 1836 my $self = shift;
186 2         6 my $index = shift;
187              
188 2         4 my $res;
189              
190             eval {
191 2         79 $res = $self->fetch( sprintf '%s/_count', $index );
192 2         7 1;
193              
194 2 50       5 } or do {
195 0         0 warn $@;
196 0         0 return [];
197             };
198              
199 2         17 return $res->{count};
200             }
201              
202             sub all {
203 0     0 1 0 my $self = shift;
204 0         0 my $index = shift;
205 0         0 my $params = shift;
206              
207             # This endpoint used to support only pluralized indices (mostly) and convert
208             # to singular indices before redispatching. Now it accepts both plural and
209             # unplural forms directly and relies on the underlying methods it
210             # dispatches to to check indices (using the global supported indices array).
211 0         0 $index =~ s/s$//;
212              
213 0 0 0     0 if ( $params and !is_hashref($params) ) {
214 0         0 croak "all: params must be a hashref";
215             }
216              
217 0 0 0     0 if ( exists $params->{fields} and !is_arrayref($params->{fields}) ) {
218 0         0 $params->{fields} = [ split /,/ => $params->{fields} ];
219             }
220              
221 0         0 return $self->$index( { __MATCH_ALL__ => 1 }, $params );
222             }
223              
224             sub download_url {
225 4     4 1 8162 my $self = shift;
226 4         13 my $module = shift;
227 4         10 my $version_or_range = shift;
228 4         10 my $dev = shift;
229              
230 4         8 my $uri = $module;
231 4         8 my @extra;
232 4 100       19 if ( defined $version_or_range ) {
233              
234 3         14 my @valid_ranges = qw{ == != <= >= < > ! };
235 3         5 my $is_using_range;
236 3         9 foreach my $range ( @valid_ranges ) {
237 15 100       49 if ( index( $version_or_range, $range ) >= 0 ) {
238 2         6 $is_using_range = 1;
239 2         19 last;
240             }
241             }
242             # by default use the '==' operator when no range set
243 3 100       11 $version_or_range = '==' . $version_or_range unless $is_using_range;
244              
245             # version=>0.21,<0.27,!=0.26&dev=1
246 3         20 push @extra, 'version=' .uri_escape_utf8($version_or_range);
247             }
248 4 100       225 if ( defined $dev ) {
249 1         7 push @extra, 'dev=' . uri_escape_utf8($dev);
250             }
251              
252 4 100       78 $uri .= '?'.join('&', @extra) if scalar @extra;
253              
254 4         17 return $self->_get( 'download_url', $uri );
255             }
256              
257             sub autocomplete {
258 0     0 1 0 my $self = shift;
259 0         0 my $q = shift;
260              
261 0         0 my $res;
262              
263             eval {
264 0         0 $res = $self->fetch( '/search/autocomplete?q=' . uri_escape_utf8($q) );
265 0         0 1;
266              
267 0 0       0 } or do {
268 0         0 warn $@;
269 0         0 return [];
270             };
271              
272             return [
273 0         0 map { $_->{fields} } @{ $res->{hits}{hits} }
  0         0  
  0         0  
274             ];
275             }
276              
277             sub autocomplete_suggest {
278 0     0 1 0 my $self = shift;
279 0         0 my $q = shift;
280              
281 0         0 my $res;
282              
283             eval {
284 0         0 $res = $self->fetch( '/search/autocomplete/suggest?q=' . uri_escape_utf8($q) );
285 0         0 1;
286              
287 0 0       0 } or do {
288 0         0 warn $@;
289 0         0 return [];
290             };
291              
292 0         0 return $res->{suggestions};
293             }
294              
295             ###
296              
297             sub _get {
298 49     49   4658 my $self = shift;
299              
300 49 100 66     781 ( scalar(@_) == 2
      66        
      66        
301             or ( scalar(@_) == 3 and ( !defined $_[2] or is_hashref($_[2]) ) ) )
302             or croak '_get takes index and search string as parameters (and an optional params hash)';
303              
304 46         83 my $index = shift;
305 46         79 my $arg = shift;
306 46         74 my $params = shift;
307              
308 46         291 my $fields_filter = $self->_read_fields( $params );
309              
310 46   50     2298 my $response = $self->fetch(
311             sprintf("%s/%s%s", $index ,$arg, $fields_filter||'')
312             );
313 46 100       3110 is_hashref($response)
314             or croak sprintf( 'Failed to fetch %s (%s)', ucfirst($index), $arg );
315              
316 45 100       204 $index = 'DownloadURL' if $index eq 'download_url';
317              
318             # deal with API response inconsistency
319 45 50 66     187 if ( $index eq 'cve' and is_hashref($response) and is_arrayref($response->{cve} ) ) {
      66        
320 1         3 $response = $response->{cve}[0];
321             }
322              
323 45         237 my $class = 'MetaCPAN::Client::' . ucfirst($index);
324 45         550 return $class->new_from_request($response, $self);
325             }
326              
327             sub _read_fields {
328 46     46   76 my $self = shift;
329 46         91 my $params = shift;
330 46 50       174 $params or return;
331              
332 0         0 my $fields = delete $params->{fields};
333 0 0       0 $fields or return;
334              
335 0 0       0 if ( is_arrayref($fields) ) {
    0          
336 0 0       0 grep { ref $_ } @$fields
  0         0  
337             and croak "fields array should not contain any refs.";
338              
339 0         0 return sprintf( "?fields=%s", join q{,} => @$fields );
340              
341             } elsif ( !ref $fields ) {
342              
343 0         0 return "?fields=$fields";
344             }
345              
346 0         0 croak "invalid param: fields";
347             }
348              
349             sub _search {
350 14     14   4261 my $self = shift;
351 14         28 my $index = shift;
352 14         28 my $args = shift;
353 14         26 my $params = shift;
354              
355 14 100       222 is_hashref($args)
356             or croak '_search takes a hash ref as query';
357              
358 13 100 100     175 ! defined $params or is_hashref($params)
359             or croak '_search takes a hash ref as query parameters';
360              
361 12   100     65 $params ||= {};
362              
363 12 100       36 grep { $_ eq $index } @supported_searches
  132         305  
364             or croak 'search index is not supported';
365              
366 11         346 my $scroller = $self->ssearch($index, $args, $params);
367              
368 11         3710 return MetaCPAN::Client::ResultSet->new(
369             scroller => $scroller,
370             index => $index,
371             );
372             }
373              
374             sub _get_or_search {
375 50     50   4052 my $self = shift;
376 50         90 my $index = shift;
377 50         87 my $arg = shift;
378 50         91 my $params = shift;
379              
380 50 100       215 is_hashref($arg) and
381             return $self->_search( $index, $arg, $params );
382              
383 42 100 66     342 defined $arg and !is_ref($arg)
384             and return $self->_get($index, $arg, $params);
385              
386 1         183 croak "$index: invalid args (takes scalar value or search parameters hashref)";
387             }
388              
389             sub _reverse_deps {
390 1     1   1 my $self = shift;
391 1         2 my $dist = shift;
392              
393 1         1 my $res;
394              
395             eval {
396 1         8 $res = $self->fetch(
397             "/reverse_dependencies/dist/$dist",
398             {
399             size => 5000,
400             query => {
401             bool => {
402             must => [
403             { term => { 'status' => 'latest' } },
404             { term => { 'authorized' => JSON->true } },
405             ]
406             }
407             },
408             }
409             );
410 1         17 1;
411              
412 1 50       2 } or do {
413 0         0 warn $@;
414 0         0 return _empty_result_set('release'),
415             };
416              
417             return MetaCPAN::Client::ResultSet->new(
418 1         20 items => $res->{'data'},
419             index => 'release',
420             );
421             }
422              
423             sub _recent {
424 0     0     my $self = shift;
425 0           my @args = @_;
426              
427 0           my $res;
428              
429             eval {
430 0           $res = $self->fetch(
431             '/release/_search',
432             {
433             from => 0,
434             query => { match_all => {} },
435             @args,
436             sort => [ { 'date' => { order => "desc" } } ],
437             }
438             );
439 0           1;
440              
441 0 0         } or do {
442 0           warn $@;
443 0           return _empty_result_set('release');
444             };
445              
446             return MetaCPAN::Client::ResultSet->new(
447 0           items => $res->{'hits'}{'hits'},
448             index => 'release',
449             );
450             }
451              
452             sub _filter_today {
453 0     0     return { range => { date => { from => "now/1d+0h" } } };
454             }
455              
456             sub _empty_result_set {
457 0     0     my $index = shift;
458              
459 0           return MetaCPAN::Client::ResultSet->new(
460             items => [],
461             index => $index,
462             );
463             }
464              
465             1;
466              
467             __END__