File Coverage

blib/lib/MetaCPAN/Client.pm
Criterion Covered Total %
statement 179 238 75.2
branch 30 56 53.5
condition 20 35 57.1
subroutine 41 49 83.6
pod 19 19 100.0
total 289 397 72.8


line stmt bran cond sub pod time code
1 19     19   2196903 use strict;
  19         35  
  19         572  
2 19     19   76 use warnings;
  19         52  
  19         1347  
3             package MetaCPAN::Client;
4             # ABSTRACT: A comprehensive, DWIM-featured client to the MetaCPAN API
5             $MetaCPAN::Client::VERSION = '2.041000';
6 19     19   8824 use Moo;
  19         117191  
  19         90  
7 19     19   24045 use Carp;
  19         48  
  19         1073  
8 19     19   7480 use JSON::MaybeXS qw< JSON >;
  19         279165  
  19         1325  
9 19     19   4895 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  19         25181  
  19         1368  
10 19     19   7419 use URI::Escape qw< uri_escape_utf8 >;
  19         27625  
  19         1214  
11              
12 19     19   8055 use MetaCPAN::Client::Request;
  19         82  
  19         757  
13 19     19   9969 use MetaCPAN::Client::Author;
  19         114  
  19         727  
14 19     19   9718 use MetaCPAN::Client::Distribution;
  19         71  
  19         623  
15 19     19   8398 use MetaCPAN::Client::DownloadURL;
  19         77  
  19         731  
16 19     19   9176 use MetaCPAN::Client::Module;
  19         84  
  19         724  
17 19     19   145 use MetaCPAN::Client::File;
  19         34  
  19         358  
18 19     19   9565 use MetaCPAN::Client::Favorite;
  19         66  
  19         638  
19 19     19   8562 use MetaCPAN::Client::Pod;
  19         103  
  19         715  
20 19     19   8995 use MetaCPAN::Client::Release;
  19         93  
  19         786  
21 19     19   10838 use MetaCPAN::Client::Mirror;
  19         90  
  19         749  
22 19     19   9461 use MetaCPAN::Client::Package;
  19         87  
  19         626  
23 19     19   7857 use MetaCPAN::Client::Permission;
  19         74  
  19         3111  
24 19     19   8104 use MetaCPAN::Client::ResultSet;
  19         59  
  19         687  
25 19     19   7803 use MetaCPAN::Client::Cover;
  19         65  
  19         606  
26 19     19   7870 use MetaCPAN::Client::CVE;
  19         71  
  19         37463  
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 5627889 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     459 );
46              
47 19         1136 return \%args;
48             }
49              
50             sub author {
51 7     7 1 255440 my $self = shift;
52 7         19 my $arg = shift;
53 7         17 my $params = shift;
54              
55 7         37 return $self->_get_or_search( 'author', $arg, $params );
56             }
57              
58             sub module {
59 2     2 1 1031 my $self = shift;
60 2         5 my $arg = shift;
61 2         3 my $params = shift;
62              
63 2         8 return $self->_get_or_search( 'module', $arg, $params );
64             }
65              
66             sub distribution {
67 31     31 1 39227 my $self = shift;
68 31         130 my $arg = shift;
69 31         67 my $params = shift;
70              
71 31         148 return $self->_get_or_search( 'distribution', $arg, $params );
72             }
73              
74             sub file {
75 1     1 1 760 my $self = shift;
76 1         2 my $arg = shift;
77 1         2 my $params = shift;
78              
79 1         3 return $self->_get_or_search( 'file', $arg, $params );
80             }
81              
82             sub package {
83 1     1 1 917 my $self = shift;
84 1         3 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 1420 my $self = shift;
92 1         3 my $arg = shift;
93 1         3 my $params = shift;
94              
95 1         5 return $self->_get_or_search( 'permission', $arg, $params );
96             }
97              
98             sub cover {
99 1     1 1 1002 my $self = shift;
100 1         3 my $arg = shift;
101 1         3 my $params = shift;
102              
103 1         4 return $self->_get_or_search( 'cover', $arg, $params );
104             }
105              
106             sub cve {
107 1     1 1 772 my $self = shift;
108 1         2 my $arg = shift;
109 1         2 my $params = shift;
110              
111 1         3 return $self->_get_or_search( 'cve', $arg, $params );
112             }
113              
114             sub pod {
115 1     1 1 842 my $self = shift;
116 1         2 my $name = shift;
117 1   50     6 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 79193 my $self = shift;
128 2         3 my $args = shift;
129 2         4 my $params = shift;
130              
131 2 50       10 is_hashref($args)
132             or croak 'favorite takes a hash ref as parameter';
133              
134 2         7 return $self->_search( 'favorite', $args, $params );
135             }
136              
137             sub release {
138 2     2 1 818 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 1065 my $self = shift;
155 1         4 my $dist = shift;
156              
157 1         4 $dist =~ s/::/-/g;
158              
159 1         5 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 is_hashref($params)
196             or croak "all: params must be a hashref";
197              
198 0 0 0     0 if ( exists $params->{fields} and !is_arrayref($params->{fields}) ) {
199 0         0 $params->{fields} = [ split /,/ => $params->{fields} ];
200             }
201              
202 0         0 return $self->$type( { __MATCH_ALL__ => 1 }, $params );
203             }
204              
205             sub download_url {
206 4     4 1 7526 my $self = shift;
207 4         10 my $module = shift;
208 4         10 my $version_or_range = shift;
209 4         9 my $dev = shift;
210              
211 4         11 my $uri = $module;
212 4         8 my @extra;
213 4 100       15 if ( defined $version_or_range ) {
214              
215 3         14 my @valid_ranges = qw{ == != <= >= < > ! };
216 3         6 my $is_using_range;
217 3         7 foreach my $range ( @valid_ranges ) {
218 15 100       39 if ( index( $version_or_range, $range ) >= 0 ) {
219 2         4 $is_using_range = 1;
220 2         4 last;
221             }
222             }
223             # by default use the '==' operator when no range set
224 3 100       10 $version_or_range = '==' . $version_or_range unless $is_using_range;
225              
226             # version=>0.21,<0.27,!=0.26&dev=1
227 3         15 push @extra, 'version=' .uri_escape_utf8($version_or_range);
228             }
229 4 100       166 if ( defined $dev ) {
230 1         4 push @extra, 'dev=' . uri_escape_utf8($dev);
231             }
232              
233 4 100       32 $uri .= '?'.join('&', @extra) if scalar @extra;
234              
235 4         16 return $self->_get( 'download_url', $uri );
236             }
237              
238             sub autocomplete {
239 0     0 1 0 my $self = shift;
240 0         0 my $q = shift;
241              
242 0         0 my $res;
243              
244             eval {
245 0         0 $res = $self->fetch( '/search/autocomplete?q=' . uri_escape_utf8($q) );
246 0         0 1;
247              
248 0 0       0 } or do {
249 0         0 warn $@;
250 0         0 return [];
251             };
252              
253             return [
254 0         0 map { $_->{fields} } @{ $res->{hits}{hits} }
  0         0  
  0         0  
255             ];
256             }
257              
258             sub autocomplete_suggest {
259 0     0 1 0 my $self = shift;
260 0         0 my $q = shift;
261              
262 0         0 my $res;
263              
264             eval {
265 0         0 $res = $self->fetch( '/search/autocomplete/suggest?q=' . uri_escape_utf8($q) );
266 0         0 1;
267              
268 0 0       0 } or do {
269 0         0 warn $@;
270 0         0 return [];
271             };
272              
273 0         0 return $res->{suggestions};
274             }
275              
276             ###
277              
278             sub _get {
279 49     49   4960 my $self = shift;
280              
281 49 100 66     854 ( scalar(@_) == 2
      66        
      66        
282             or ( scalar(@_) == 3 and ( !defined $_[2] or is_hashref($_[2]) ) ) )
283             or croak '_get takes type and search string as parameters (and an optional params hash)';
284              
285 46         107 my $type = shift;
286 46         84 my $arg = shift;
287 46         79 my $params = shift;
288              
289 46         255 my $fields_filter = $self->_read_fields( $params );
290              
291 46   50     1787 my $response = $self->fetch(
292             sprintf("%s/%s%s", $type ,$arg, $fields_filter||'')
293             );
294 46 100       2970 is_hashref($response)
295             or croak sprintf( 'Failed to fetch %s (%s)', ucfirst($type), $arg );
296              
297 45 100       217 $type = 'DownloadURL' if $type eq 'download_url';
298 45         266 my $class = 'MetaCPAN::Client::' . ucfirst($type);
299              
300 45 50 66     220 if ( $type eq 'cve' and is_hashref($response) and is_arrayref($response->{cve} ) ) {
      66        
301 1         7 $class =~ s/Cve/CVE/;
302 1         5 $response = $response->{cve}[0];
303             }
304              
305 45         562 return $class->new_from_request($response, $self);
306             }
307              
308             sub _read_fields {
309 46     46   82 my $self = shift;
310 46         97 my $params = shift;
311 46 50       203 $params or return;
312              
313 0         0 my $fields = delete $params->{fields};
314 0 0       0 $fields or return;
315              
316 0 0       0 if ( is_arrayref($fields) ) {
    0          
317 0 0       0 grep { ref $_ } @$fields
  0         0  
318             and croak "fields array should not contain any refs.";
319              
320 0         0 return sprintf( "?fields=%s", join q{,} => @$fields );
321              
322             } elsif ( !ref $fields ) {
323              
324 0         0 return "?fields=$fields";
325             }
326              
327 0         0 croak "invalid param: fields";
328             }
329              
330             sub _search {
331 14     14   5761 my $self = shift;
332 14         29 my $type = shift;
333 14         24 my $args = shift;
334 14         45 my $params = shift;
335              
336 14 100       222 is_hashref($args)
337             or croak '_search takes a hash ref as query';
338              
339 13 100 100     253 ! defined $params or is_hashref($params)
340             or croak '_search takes a hash ref as query parameters';
341              
342 12   100     70 $params ||= {};
343              
344 12 100       36 grep { $_ eq $type } @supported_searches
  132         478  
345             or croak 'search type is not supported';
346              
347 11         339 my $scroller = $self->ssearch($type, $args, $params);
348              
349 11         4555 return MetaCPAN::Client::ResultSet->new(
350             scroller => $scroller,
351             type => $type,
352             );
353             }
354              
355             sub _get_or_search {
356 50     50   6927 my $self = shift;
357 50         101 my $type = shift;
358 50         119 my $arg = shift;
359 50         109 my $params = shift;
360              
361 50 100       260 is_hashref($arg) and
362             return $self->_search( $type, $arg, $params );
363              
364 42 100 66     435 defined $arg and !is_ref($arg)
365             and return $self->_get($type, $arg, $params);
366              
367 1         218 croak "$type: invalid args (takes scalar value or search parameters hashref)";
368             }
369              
370             sub _reverse_deps {
371 1     1   1 my $self = shift;
372 1         2 my $dist = shift;
373              
374 1         1 my $res;
375              
376             eval {
377 1         8 $res = $self->fetch(
378             "/reverse_dependencies/dist/$dist",
379             {
380             size => 5000,
381             query => {
382             bool => {
383             must => [
384             { term => { 'status' => 'latest' } },
385             { term => { 'authorized' => JSON->true } },
386             ]
387             }
388             },
389             }
390             );
391 1         16 1;
392              
393 1 50       2 } or do {
394 0         0 warn $@;
395 0         0 return _empty_result_set('release'),
396             };
397              
398             return MetaCPAN::Client::ResultSet->new(
399 1         21 items => $res->{'data'},
400             type => 'release',
401             );
402             }
403              
404             sub _recent {
405 0     0     my $self = shift;
406 0           my @args = @_;
407              
408 0           my $res;
409              
410             eval {
411 0           $res = $self->fetch(
412             '/release/_search',
413             {
414             from => 0,
415             query => { match_all => {} },
416             @args,
417             sort => [ { 'date' => { order => "desc" } } ],
418             }
419             );
420 0           1;
421              
422 0 0         } or do {
423 0           warn $@;
424 0           return _empty_result_set('release');
425             };
426              
427             return MetaCPAN::Client::ResultSet->new(
428 0           items => $res->{'hits'}{'hits'},
429             type => 'release',
430             );
431             }
432              
433             sub _filter_today {
434 0     0     return { range => { date => { from => "now/1d+0h" } } };
435             }
436              
437             sub _empty_result_set {
438 0     0     my $type = shift;
439              
440 0           return MetaCPAN::Client::ResultSet->new(
441             items => [],
442             type => $type,
443             );
444             }
445              
446             1;
447              
448             __END__