File Coverage

lib/Net/API/CPAN.pm
Criterion Covered Total %
statement 59 669 8.8
branch 8 586 1.3
condition 3 483 0.6
subroutine 18 76 23.6
pod 37 37 100.0
total 125 1851 6.7


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Meta CPAN API - /usr/local/src/perl/Net-API-CPAN/lib/Net/API/CPAN.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2023 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2023/07/25
7             ## Modified 2023/07/25
8             ## All rights reserved
9             ##
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package Net::API::CPAN;
15             BEGIN
16             {
17 3     3   4505869 use strict;
  3         19  
  3         107  
18 3     3   19 use warnings;
  3         6  
  3         109  
19 3     3   17 use warnings::register;
  3         6  
  3         562  
20 3     3   541 use parent qw( Module::Generic );
  3         366  
  3         29  
21 3     3   120921 use vars qw( $VERSION $UA_OPTS $TYPE2CLASS $MODULE_RE );
  3         6  
  3         218  
22 3     3   1912 use HTTP::Promise;
  3         19370658  
  3         64  
23 3     3   1312 use HTTP::Promise::Headers;
  3         7  
  3         91  
24 3     3   19 use JSON;
  3         5  
  3         34  
25             use constant
26             {
27 3         376 API_URI => 'https://fastapi.metacpan.org',
28             METACPAN_CLIENTINFO_URI => 'https://clientinfo.metacpan.org',
29 3     3   476 };
  3         10  
30 3     3   25 our $MODULE_RE = qr/[a-zA-Z_][a-zA-Z0-9_]+(?:\:{2}[a-zA-Z0-9_]+)*/;
31 3         68 our $VERSION = 'v0.1.0';
32             };
33              
34 3     3   23 use strict;
  3         5  
  3         83  
35 3     3   15 use warnings;
  3         8  
  3         30022  
36              
37             our $UA_OPTS =
38             {
39             agent => "MetaCPAN API Client/$VERSION",
40             auto_switch_https => 1,
41             default_headers => HTTP::Promise::Headers->new(
42             Accept => 'application/json,text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
43             ),
44             ext_vary => 1,
45             max_body_in_memory_size => 102400,
46             timeout => 15,
47             use_promise => 0,
48             };
49              
50             our $TYPE2CLASS =
51             {
52             activity => 'Net::API::CPAN::Activity',
53             author => 'Net::API::CPAN::Author',
54             changes => 'Net::API::CPAN::Changes',
55             changes_release => 'Net::API::CPAN::Changes::Release',
56             contributor => 'Net::API::CPAN::Contributor',
57             cover => 'Net::API::CPAN::Cover',
58             diff => 'Net::API::CPAN::Diff',
59             distribution => 'Net::API::CPAN::Distribution',
60             download_url => 'Net::API::CPAN::DownloadUrl',
61             favorite => 'Net::API::CPAN::Favorite',
62             file => 'Net::API::CPAN::File',
63             list_web => 'Net::API::CPAN::List::Web',
64             list => 'Net::API::CPAN::List',
65             mirror => 'Net::API::CPAN::Mirror',
66             mirrors => 'Net::API::CPAN::Mirrors',
67             module => 'Net::API::CPAN::Module',
68             package => 'Net::API::CPAN::Package',
69             permission => 'Net::API::CPAN::Permission',
70             # pod => 'Net::API::CPAN::Pod',
71             rating => 'Net::API::CPAN::Rating',
72             release => 'Net::API::CPAN::Release',
73             release_recent => 'Net::API::CPAN::Release::Recent',
74             release_suggest => 'Net::API::CPAN::Release::Suggest',
75             };
76              
77             sub init
78             {
79 2     2 1 426 my $self = shift( @_ );
80 2 50       118 $self->{api_version} = 1 unless( CORE::exists( $self->{api_version} ) );
81 2 50       10 $self->{cache_file} = undef unless( CORE::exists( $self->{cache_file} ) );
82 2 50       9 $self->{ua} = undef unless( CORE::exists( $self->{ua} ) );
83 2         6 $self->{_init_strict_use_sub} = 1;
84 2         62 $self->{_exception_class} = 'Net::API::CPAN::Exception';
85 2 50       24 $self->SUPER::init( @_ ) || return( $self->pass_error );
86 2 50 33     305 unless( CORE::exists( $self->{ua} ) && $self->_is_a( $self->{ua} => 'HTTP::Promise' ) )
87             {
88 2   50     38 $self->{ua} = HTTP::Promise->new( %$UA_OPTS, debug => $self->debug ) ||
89             return( $self->pass_error( HTTP::Promise->error ) );
90            
91             }
92 2 50       159370 $self->{api_version} = 1 unless( $self->{api_version} =~ /^\d+$/ );
93 2 50       12 unless( $self->{api_uri} )
94             {
95 2         16 $self->api_uri( API_URI . '/v' . $self->{api_version} );
96             }
97 2         10289 return( $self );
98             }
99              
100             sub activity
101             {
102 0     0 1 0 my $self = shift( @_ );
103 0         0 my $opts = $self->_get_args_as_hash( @_ );
104 0 0 0     0 if( exists( $opts->{author} ) && length( $opts->{author} // '' ) )
      0        
105             {
106             return( $self->fetch( 'activity' => {
107             endpoint => "/activity",
108             class => $self->_object_type_to_class( 'activity' ),
109             query => {
110             author => uc( $opts->{author} ),
111             ( exists( $opts->{interval} ) ? ( res => $opts->{interval} ) : () ),
112 0 0       0 ( exists( $opts->{new} ) ? ( new_dists => 'n' ) : () ),
    0          
113             },
114             }) );
115             }
116 0 0 0     0 if( exists( $opts->{distribution} ) && length( $opts->{distribution} // '' ) )
    0 0        
    0 0        
      0        
117             {
118             return( $self->fetch( 'activity' => {
119             endpoint => "/activity",
120             class => $self->_object_type_to_class( 'activity' ),
121             query => {
122             distribution => $opts->{distribution},
123 0 0       0 ( exists( $opts->{interval} ) ? ( res => $opts->{interval} ) : () ),
124             },
125             }) );
126             }
127             elsif( exists( $opts->{module} ) && length( $opts->{module} // '' ) )
128             {
129             return( $self->fetch( 'activity' => {
130             endpoint => "/activity",
131             class => $self->_object_type_to_class( 'activity' ),
132             query => {
133             module => $opts->{module},
134             ( exists( $opts->{interval} ) ? ( res => $opts->{interval} ) : () ),
135 0 0       0 ( exists( $opts->{new} ) ? ( new_dists => 'n' ) : () ),
    0          
136             },
137             }) );
138             }
139             elsif( exists( $opts->{new} ) )
140             {
141             return( $self->fetch( 'activity' => {
142             endpoint => "/activity",
143             class => $self->_object_type_to_class( 'activity' ),
144             query => {
145             new_dists => 'n',
146 0 0       0 ( exists( $opts->{interval} ) ? ( res => $opts->{interval} ) : () ),
147             },
148             }) );
149             }
150             else
151             {
152 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
153             }
154             }
155              
156 3     3 1 18221 sub api_uri { return( shift->_set_get_uri( 'api_uri', @_ ) ); }
157              
158 1     1 1 1397 sub api_version { return( shift->_set_get_scalar_as_object( 'api_version', @_ ) ); }
159              
160             sub author
161             {
162 0     0 1 0 my $self = shift( @_ );
163 0         0 my( $author, $authors, $filter, $opts );
164 0 0       0 if( @_ )
165             {
166 0 0 0     0 if( scalar( @_ ) == 1 &&
    0 0        
    0 0        
      0        
167             $self->_is_array( $_[0] ) )
168             {
169 0         0 $authors = shift( @_ );
170 0         0 return( $self->fetch( author => {
171             endpoint => "/author/by_ids",
172             class => $self->_object_type_to_class( 'list' ),
173             query => { id => [@$authors] },
174             }) );
175             }
176             elsif( scalar( @_ ) == 1 &&
177             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
178             {
179 0         0 $filter = shift( @_ );
180 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
181             return( $self->pass_error( $filter->error ) );
182 0         0 return( $self->fetch( author => {
183             endpoint => "/author",
184             class => $self->_object_type_to_class( 'list' ),
185             args => {
186             filter => $filter,
187             },
188             method => 'post',
189             payload => $payload,
190             }) );
191             }
192             elsif( scalar( @_ ) == 1 &&
193             ( !ref( $_[0] ) || ( ref( $_[0] ) && overload::Method( $_[0] => '""' ) ) ) )
194             {
195 0         0 $author = uc( shift( @_ ) );
196 0         0 return( $self->fetch( author => {
197             endpoint => "/author/${author}",
198             class => $self->_object_type_to_class( 'author' ),
199             }) );
200             }
201             else
202             {
203 0         0 $opts = $self->_get_args_as_hash( @_ );
204 0 0       0 if( exists( $opts->{query} ) )
    0          
    0          
205             {
206             return( $self->fetch( author => {
207             endpoint => "/author",
208             class => $self->_object_type_to_class( 'list' ),
209             query => {
210             'q' => $opts->{query},
211             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
212 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
213             }
214             }) );
215             }
216             elsif( exists( $opts->{prefix} ) )
217             {
218             return( $self->fetch( author => {
219             endpoint => "/author/by_prefix/" . $opts->{prefix},
220 0         0 class => $self->_object_type_to_class( 'list' ),
221             }) );
222             }
223             elsif( exists( $opts->{user} ) )
224             {
225 0 0       0 my $users = $self->_is_array( $opts->{user} ) ? $opts->{user} : [$opts->{user}];
226 0 0       0 if( scalar( @$users ) > 1 )
    0          
227             {
228 0         0 return( $self->fetch( author => {
229             endpoint => "/author/by_user",
230             class => $self->_object_type_to_class( 'list' ),
231             query => {
232             user => [@$users],
233             },
234             }) );
235             }
236             elsif( scalar( @$users ) )
237             {
238 0         0 return( $self->fetch( author => {
239             endpoint => "/author/by_user/" . $users->[0],
240             class => $self->_object_type_to_class( 'list' ),
241             }) );
242             }
243             else
244             {
245 0         0 return( $self->error( "No user ID was provided." ) );
246             }
247             }
248             else
249             {
250 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
251             }
252             }
253             }
254             else
255             {
256 0         0 return( $self->fetch( 'author' => {
257             endpoint => "/author",
258             class => $self->_object_type_to_class( 'list' ),
259             }) );
260             }
261             }
262              
263             sub autocomplete
264             {
265 0     0 1 0 my $self = shift( @_ );
266 0         0 my $term = shift( @_ );
267 0 0       0 return( $self->error( "No search term was provided." ) ) if( $self->_is_empty( $term ) );
268             return( $self->fetch( 'file' => {
269             endpoint => "/search/autocomplete",
270             class => $self->_object_type_to_class( 'list' ),
271             query => {
272             'q' => $term,
273             },
274             # The data returned by the API, although containing module information, are not formatted like we expect it to be, so we set this callback to correct that, so that Net::API::CPAN::List->load_data() is happy
275             list_preprocess => sub
276             {
277 0   0 0   0 my $ref = shift( @_ ) ||
278             die( "No autcomplete data was provided to preprocess.\n" );
279 0 0 0     0 if( defined( $ref ) &&
      0        
      0        
      0        
      0        
280             ref( $ref ) eq 'HASH' &&
281             exists( $ref->{hits} ) &&
282             ref( $ref->{hits} ) eq 'HASH' &&
283             exists( $ref->{hits}->{hits} ) &&
284             ref( $ref->{hits}->{hits} ) eq 'ARRAY' )
285             {
286             # For each entry, there is one element called 'fields' containing the properties distribution, documentation, release and author. We rename that element 'fields' to '_source' to standardise.
287 0         0 for( my $i = 0; $i < scalar( @{$ref->{hits}->{hits}} ); $i++ )
  0         0  
288             {
289 0         0 my $this = $ref->{hits}->{hits}->[$i];
290             # $self->message( 5, "Processing data at offset $i -> ", sub{ $self->Module::Generic::dump( $this ) } );
291 0 0 0     0 if( ref( $this ) eq 'HASH' &&
      0        
292             exists( $this->{fields} ) &&
293             ref( $this->{fields} ) eq 'HASH' )
294             {
295 0         0 $this->{_source} = delete( $this->{fields} );
296 0         0 $ref->{hits}->{hits}->[$i] = $this;
297             }
298             else
299             {
300 0 0       0 warn( "Warning only: I was expecting the property 'fields' to be present for this autocomplete data at offset $i, but could not find it, or it is not an HASH reference: ", $self->Module::Generic::dump( $this ) ) if( $self->_is_warnings_enabled );
301             }
302             }
303             }
304             else
305             {
306 0 0       0 warn( "Warning only: autocomplete data provided for preprocessing is not an hash reference or does not contains the property path hits->hits as an array: ", $self->Module::Generic::dump( $ref ) ) if( $self->_is_warnings_enabled );
307             }
308 0         0 return( $ref );
309             },
310 0         0 }) );
311             }
312              
313 0     0 1 0 sub cache_file { return( shift->_set_get_file( 'cache_file', @_ ) ); }
314              
315             sub changes
316             {
317 0     0 1 0 my $self = shift( @_ );
318 0         0 my $opts = $self->_get_args_as_hash( @_ );
319 0 0 0     0 if( exists( $opts->{distribution} ) )
    0 0        
    0          
320             {
321 0 0       0 return( $self->error( "Distribution provided is empty." ) ) if( $self->_is_empty( $opts->{distribution} ) );
322             return( $self->fetch( changes => {
323             endpoint => "/changes/" . $opts->{distribution},
324 0         0 class => $self->_object_type_to_class( 'changes' ),
325             }) );
326             }
327             elsif( exists( $opts->{author} ) && exists( $opts->{release} ) )
328             {
329 0 0 0     0 if( $self->_is_array( $opts->{author} ) &&
330             $self->_is_array( $opts->{release} ) )
331             {
332 0 0       0 if( scalar( @{$opts->{author}} ) != scalar( @{$opts->{release} } ) )
  0         0  
  0         0  
333             {
334 0         0 return( $self->error( "The size of the array for author (", scalar( @{$opts->{author}} ), ") is not the same as the size of the array for release (", scalar( @{$opts->{release}} ), ")." ) );
  0         0  
  0         0  
335             }
336 0         0 my $n = -1;
337             return( $self->fetch( changes_release => {
338             endpoint => "/changes/by_releases",
339             class => $self->_object_type_to_class( 'list' ),
340             args => {
341             pageable => 0,
342             },
343             query => {
344 0         0 release => [map( join( '/', $opts->{author}->[++$n],$opts->{release}->[$n] ), @{$opts->{author}} )],
  0         0  
345             }
346             }) );
347             }
348             else
349             {
350 0 0       0 if( $self->_is_empty( $opts->{author} ) )
    0          
351             {
352 0         0 return( $self->error( "Author provided is empty." ) );
353             }
354             elsif( $self->_is_empty( $opts->{release} ) )
355             {
356 0         0 return( $self->error( "Release provided is empty." ) );
357             }
358             return( $self->fetch( changes => {
359             endpoint => "/changes/" . $opts->{author} . '/' . $opts->{release},
360 0         0 class => $self->_object_type_to_class( 'changes' ),
361             }) );
362             }
363             }
364             # Example: OALDERS/HTTP-Message-6.36
365             # or
366             # [qw( OALDERS/HTTP-Message-6.36 NEILB/Data-HexDump-0.04 )]
367             elsif( exists( $opts->{release} ) &&
368             defined( $opts->{release} ) )
369             {
370 0 0       0 if( $self->_is_array( $opts->{release} ) )
371             {
372             return( $self->fetch( changes_release => {
373             endpoint => "/changes/by_releases",
374             class => $self->_object_type_to_class( 'list' ),
375             args => {
376             pageable => 0,
377             },
378             query => {
379 0         0 release => [@{$opts->{release}}],
  0         0  
380             }
381             }) );
382             }
383             else
384             {
385             return( $self->fetch( changes => {
386             endpoint => "/changes/" . $opts->{release},
387 0         0 class => $self->_object_type_to_class( 'changes' ),
388             }) );
389             }
390             }
391             else
392             {
393 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
394             }
395             }
396              
397             # HTTP request returns something like this:
398             # {
399             # "production": {
400             # "version": "v1",
401             # "domain": "https://fastapi.metacpan.org/",
402             # "url": "https://fastapi.metacpan.org/v1/"
403             # },
404             # "future": {
405             # "version": "v1",
406             # "domain": "https://fastapi.metacpan.org/",
407             # "url": "https://fastapi.metacpan.org/v1/"
408             # },
409             # "testing": {
410             # "version": "v1",
411             # "domain": "https://fastapi.metacpan.org/",
412             # "url": "https://fastapi.metacpan.org/v1/"
413             # }
414             # }
415             sub clientinfo
416             {
417 0     0 1 0 my $self = shift( @_ );
418 0 0 0     0 return( $self->{_cached_clientinfo} ) if( exists( $self->{_cached_clientinfo} ) && defined( $self->{_cached_clientinfo} ) );
419 0         0 my $resp = $self->ua->get( METACPAN_CLIENTINFO_URI );
420 0         0 my $info = {};
421 0 0       0 if( $resp->is_success )
422             {
423 0         0 my $payload = $resp->decoded_content_utf8;
424 0         0 my $j = $self->new_json;
425 0         0 local $@;
426             # try-catch
427             eval
428 0         0 {
429 0         0 $info = $j->decode( $payload );
430             };
431 0 0       0 if( $@ )
432             {
433 0 0       0 warn( "Warning only: error decoding the JSON payload returned by the MetaCPAN API: $@" ) if( $self->_is_warnings_enabled );
434             }
435             }
436            
437 0 0       0 unless( scalar( keys( %$info ) ) )
438             {
439 0         0 $info =
440             {
441             production =>
442             {
443             domain => API_URI,
444             url => API_URI,
445             }
446             };
447             }
448            
449 0         0 foreach my $stage ( keys( %$info ) )
450             {
451 0         0 foreach my $prop ( keys( %{$info->{ $stage }} ) )
  0         0  
452             {
453 0 0 0     0 if( defined( $info->{ $stage }->{ $prop } ) &&
      0        
454             length( $info->{ $stage }->{ $prop } ) &&
455             lc( substr( $info->{ $stage }->{ $prop }, 0, 4 ) ) eq 'http' )
456             {
457 0         0 $info->{ $stage }->{ $prop } = URI->new( $info->{ $stage }->{ $prop } );
458             }
459             }
460             }
461 0         0 $self->{_cached_clientinfo} = $info;
462 0         0 return( $info );
463             }
464              
465             sub contributor
466             {
467 0     0 1 0 my $self = shift( @_ );
468 0         0 my $opts = $self->_get_args_as_hash( @_ );
469 0 0 0     0 if( exists( $opts->{author} ) &&
    0          
470             exists( $opts->{release} ) )
471             {
472 0 0       0 if( $self->_is_empty( $opts->{author} ) )
    0          
473             {
474 0         0 return( $self->error( "Author provided is empty." ) );
475             }
476             elsif( $self->_is_empty( $opts->{release} ) )
477             {
478 0         0 return( $self->error( "Release provided is empty." ) );
479             }
480             return( $self->fetch( contributor => {
481             endpoint => "/contributor/" . $opts->{author} . '/' . $opts->{release},
482 0         0 class => $self->_object_type_to_class( 'list' ),
483             }) );
484             }
485             elsif( exists( $opts->{author} ) )
486             {
487 0 0       0 if( $self->_is_empty( $opts->{author} ) )
488             {
489 0         0 return( $self->error( "Author provided is empty." ) );
490             }
491             return( $self->fetch( contributor => {
492             endpoint => "/contributor/by_pauseid/" . $opts->{author},
493 0         0 class => $self->_object_type_to_class( 'list' ),
494             args => {
495             pageable => 0,
496             },
497             }) );
498             }
499             else
500             {
501 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
502             }
503             }
504              
505             sub cover
506             {
507 0     0 1 0 my $self = shift( @_ );
508 0         0 my $opts = $self->_get_args_as_hash( @_ );
509 0 0       0 if( exists( $opts->{release} ) )
510             {
511 0 0       0 if( $self->_is_empty( $opts->{release} ) )
512             {
513 0         0 return( $self->error( "Release provided is empty." ) );
514             }
515             return( $self->fetch( cover => {
516             endpoint => "/cover/" . $opts->{release},
517 0         0 class => $self->_object_type_to_class( 'cover' ),
518             }) );
519             }
520             else
521             {
522 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
523             }
524             }
525              
526             sub diff
527             {
528 0     0 1 0 my $self = shift( @_ );
529 0         0 my $opts = $self->_get_args_as_hash( @_ );
530 0 0       0 my $type = ( exists( $opts->{accept} ) ? $opts->{accept} : 'application/json' );
531 0 0 0     0 if( exists( $opts->{file1} ) &&
    0 0        
    0 0        
532             exists( $opts->{file2} ) )
533             {
534 0 0       0 if( $self->_is_empty( $opts->{file1} ) )
    0          
535             {
536 0         0 return( $self->error( "File1 provided is empty." ) );
537             }
538             elsif( $self->_is_empty( $opts->{file2} ) )
539             {
540 0         0 return( $self->error( "File2 provided is empty." ) );
541             }
542             return( $self->fetch( diff => {
543             endpoint => "/diff/file/" . join( '/', @$opts{qw( file1 file2 )} ),
544 0 0   0   0 class => ( $type eq 'text/plain' ? sub{$_[0]} : $self->_object_type_to_class( 'diff' ) ),
  0         0  
545             headers => [Accept => $type],
546             # The MetaCPAN REST API recognise the Accept header only with the POST method,
547             # not the GET method, amazingly enough
548             # See <https://github.com/metacpan/metacpan-api/blob/master/lib/MetaCPAN/Server/Controller/Diff.pm>
549             # and Catalyst::TraitFor::Request::REST for more details on this.
550             method => 'post',
551             }) );
552             }
553             elsif( exists( $opts->{author1} ) &&
554             exists( $opts->{release1} ) &&
555             exists( $opts->{release2} ) )
556             {
557 0   0     0 $opts->{author2} //= $opts->{author1};
558 0         0 foreach my $t ( qw( author1 author2 release1 release2 ) )
559             {
560 0 0       0 return( $self->error( "$t option provided is empty" ) ) if( $self->_is_empty( $opts->{ $t } ) );
561             }
562             return( $self->fetch( diff => {
563             endpoint => "/diff/release/" . join( '/', @$opts{qw( author1 release1 author2 release2 )} ),
564 0 0   0   0 class => ( $type eq 'text/plain' ? sub{$_[0]} : $self->_object_type_to_class( 'diff' ) ),
  0         0  
565             headers => [Accept => $type],
566             # The MetaCPAN REST API recognise the Accept header only with the POST method,
567             # not the GET method, amazingly enough
568             # See <https://github.com/metacpan/metacpan-api/blob/master/lib/MetaCPAN/Server/Controller/Diff.pm>
569             # and Catalyst::TraitFor::Request::REST for more details on this.
570             method => 'post',
571             }) );
572             }
573             elsif( exists( $opts->{distribution} ) )
574             {
575 0 0       0 return( $self->error( "Distribution provided is empty." ) ) if( $self->_is_empty( $opts->{distribution} ) );
576             return( $self->fetch( diff => {
577             endpoint => "/diff/release/" . $opts->{distribution},
578 0 0   0   0 class => ( $type eq 'text/plain' ? sub{$_[0]} : $self->_object_type_to_class( 'diff' ) ),
  0         0  
579             headers => [Accept => $type],
580             # The MetaCPAN REST API recognise the Accept header only with the POST method,
581             # not the GET method, amazingly enough
582             # See <https://github.com/metacpan/metacpan-api/blob/master/lib/MetaCPAN/Server/Controller/Diff.pm>
583             # and Catalyst::TraitFor::Request::REST for more details on this.
584             method => 'post',
585             }) );
586             }
587             else
588             {
589 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
590             }
591             }
592              
593             sub distribution
594             {
595 0     0 1 0 my $self = shift( @_ );
596 0 0       0 if( @_ )
597             {
598 0         0 my( $dist, $filter, $opts );
599 0 0 0     0 if( scalar( @_ ) == 1 &&
    0 0        
      0        
600             ( !ref( $_[0] ) || ( ref( $_[0] ) && overload::Method( $_[0] => '""' ) ) ) )
601             {
602 0         0 $dist = shift( @_ );
603 0         0 return( $self->fetch( distribution => {
604             endpoint => "/distribution/${dist}",
605             class => $self->_object_type_to_class( 'distribution' ),
606             }) );
607             }
608             elsif( scalar( @_ ) == 1 &&
609             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
610             {
611 0         0 $filter = shift( @_ );
612 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
613             return( $self->pass_error( $filter->error ) );
614 0         0 return( $self->fetch( distribution => {
615             endpoint => "/distribution",
616             class => $self->_object_type_to_class( 'list' ),
617             args => {
618             filter => $filter,
619             },
620             method => 'post',
621             payload => $payload,
622             }) );
623             }
624             else
625             {
626 0         0 $opts = $self->_get_args_as_hash( @_ );
627 0 0       0 if( exists( $opts->{query} ) )
628             {
629             return( $self->fetch( distribution => {
630             endpoint => "/distribution",
631             class => $self->_object_type_to_class( 'list' ),
632             query => {
633             'q' => $opts->{query},
634             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
635 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
636             }
637             }) );
638             }
639             else
640             {
641 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
642             }
643             }
644             }
645             else
646             {
647 0         0 return( $self->fetch( distribution => {
648             endpoint => "/distribution",
649             class => $self->_object_type_to_class( 'list' ),
650             }) );
651             }
652             }
653              
654             sub download_url
655             {
656 0     0 1 0 my $self = shift( @_ );
657 0   0     0 my $mod = shift( @_ ) ||
658             return( $self->error( "No module provided to retrieve its download URL." ) );
659 0         0 my $opts = $self->_get_args_as_hash( @_ );
660             return( $self->fetch( download_url => {
661             endpoint => "/download_url/" . $mod,
662             class => $self->_object_type_to_class( 'download_url' ),
663             query => {
664             ( $opts->{dev} ? ( dev => 1 ) : () ),
665 0 0       0 ( $opts->{version} ? ( version => $opts->{version} ) : () ),
    0          
666             },
667             }) );
668             }
669              
670             sub favorite
671             {
672 0     0 1 0 my $self = shift( @_ );
673 0 0       0 if( @_ )
674             {
675 0         0 my( $filter, $opts );
676 0 0 0     0 if( scalar( @_ ) == 1 &&
677             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
678             {
679 0         0 $filter = shift( @_ );
680 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
681             return( $self->pass_error( $filter->error ) );
682 0         0 return( $self->fetch( favorite => {
683             endpoint => "/favorite",
684             class => $self->_object_type_to_class( 'list' ),
685             args => {
686             filter => $filter,
687             },
688             method => 'post',
689             payload => $payload,
690             }) );
691             }
692             else
693             {
694 0         0 $opts = $self->_get_args_as_hash( @_ );
695 0 0 0     0 if( exists( $opts->{query} ) )
    0          
    0          
    0          
    0          
    0          
696             {
697             return( $self->fetch( favorite => {
698             endpoint => "/favorite",
699             class => $self->_object_type_to_class( 'list' ),
700             query => {
701             'q' => $opts->{query},
702             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
703 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
704             }
705             }) );
706             }
707             elsif( exists( $opts->{aggregate} ) ||
708             exists( $opts->{agg} ) )
709             {
710             # $agg could be a distribution or an array reference of distributions
711 0   0     0 my $agg = $opts->{aggregate} // $opts->{agg};
712 0 0       0 return( $self->error( "Aggregate value provided is empty." ) ) if( $self->_is_empty( $agg ) );
713             return( $self->fetch( favorite => {
714             endpoint => "/favorite/agg_by_distributions",
715             # class => $self->_object_type_to_class( 'list' ),
716             class => sub
717             {
718 0     0   0 my $ref = shift( @_ );
719 0 0 0     0 if( ref( $ref ) eq 'HASH' &&
      0        
720             exists( $ref->{favorites} ) &&
721             ref( $ref->{favorites} ) eq 'HASH' )
722             {
723 0         0 return( $ref->{favorites} );
724             }
725             # Return an empty hash for uniformity
726 0         0 return( {} );
727             },
728 0         0 query => { distribution => [@$agg] },
729             }) );
730             }
731             elsif( exists( $opts->{distribution} ) )
732             {
733 0 0       0 return( $self->error( "Distribution value provided is empty." ) ) if( $self->_is_empty( $opts->{distribution} ) );
734             return( $self->fetch( favorite => {
735             endpoint => "/favorite/users_by_distribution/" . $opts->{distribution},
736             # class => $self->_object_type_to_class( 'list' ),
737             class => sub
738             {
739 0     0   0 my $ref = shift( @_ );
740 0 0 0     0 if( ref( $ref ) eq 'HASH' &&
      0        
741             exists( $ref->{users} ) &&
742             ref( $ref->{users} ) eq 'ARRAY' )
743             {
744 0         0 return( $ref->{users} );
745             }
746 0         0 return( [] );
747             },
748 0         0 }) );
749             }
750             elsif( exists( $opts->{user} ) )
751             {
752 0 0       0 return( $self->error( "User value provided is empty." ) ) if( $self->_is_empty( $opts->{user} ) );
753             return( $self->fetch( favorite => {
754             endpoint => "/favorite/by_user/" . $opts->{user},
755 0         0 class => $self->_object_type_to_class( 'list' ),
756             }) );
757             }
758             elsif( exists( $opts->{leaderboard} ) )
759             {
760             return( $self->fetch( favorite => {
761             endpoint => "/favorite/leaderboard",
762             # class => $self->_object_type_to_class( 'list' ),
763             class => sub
764             {
765 0     0   0 my $ref = shift( @_ );
766 0         0 my $data = [];
767 0 0 0     0 if( ref( $ref ) eq 'HASH' &&
      0        
768             exists( $ref->{leaderboard} ) &&
769             ref( $ref->{leaderboard} ) eq 'ARRAY' )
770             {
771 0         0 return( $ref->{leaderboard} );
772             }
773             # Return an empty array for uniformity
774 0         0 return( [] );
775             },
776 0         0 }) );
777             }
778             elsif( exists( $opts->{recent} ) )
779             {
780 0         0 return( $self->fetch( favorite => {
781             endpoint => "/favorite/recent",
782             class => $self->_object_type_to_class( 'list' ),
783             }) );
784             }
785             else
786             {
787 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
788             }
789             }
790             }
791             else
792             {
793 0         0 return( $self->fetch( 'favorite' => {
794             endpoint => "/favorite",
795             class => $self->_object_type_to_class( 'list' ),
796             }) );
797             }
798             }
799              
800             sub fetch
801             {
802 0     0 1 0 my $self = shift( @_ );
803 0   0     0 my $type = shift( @_ ) || return( $self->error( "No object type was provided." ) );
804 0 0       0 return( $self->error( "Object type contains illegal characters." ) ) if( $type !~ /^\w+$/ );
805 0         0 my $opts = $self->_get_args_as_hash( @_ );
806             # $self->message( 4, "Options received are: ", sub{ $self->dump( $opts ) } );
807 0   0     0 my $class = $opts->{class} || $self->_object_type_to_class( $type ) ||
808             return( $self->pass_error );
809 0         0 my( $ep, $meth, $uri, $req );
810 0 0       0 if( exists( $opts->{request} ) )
811             {
812 0 0       0 if( !$self->_is_a( $opts->{request} => 'HTTP::Promise::Request' ) )
813             {
814 0         0 return( $self->error( "Request provided is not an HTTP::Promise::Request object." ) );
815             }
816 0         0 $req = $opts->{request};
817 0   0     0 $uri = $req->uri || return( $self->error( "No URI set in request object provided." ) );
818 0   0     0 $meth = $req->method || return( $self->error( "No HTTP method set in request object provided." ) );
819             }
820             else
821             {
822             $ep = $opts->{endpoint} ||
823 0   0     0 return( $self->error( "No endpoint was provided." ) );
824 0   0     0 $meth = $opts->{method} // 'get';
825 0         0 $uri = $self->api_uri->clone;
826 0 0       0 $uri->path( $uri->path . ( substr( $ep, 0, 1 ) eq '/' ? '' : '/' ) . $ep );
827             }
828 0   0     0 my $ua = $self->ua || return( $self->error( "The User Agent object is gone!" ) );
829 0         0 my $postprocess;
830 0 0       0 if( $self->_is_code( $opts->{postprocess} ) )
831             {
832 0         0 $postprocess = $opts->{postprocess};
833             }
834 0         0 my( $headers, $payload, $query );
835 0 0       0 if( exists( $opts->{headers} ) )
836             {
837 0 0       0 return( $self->error( "Headers option provided is not an array reference." ) ) if( !$self->_is_array( $opts->{headers} ) );
838 0         0 $headers = $opts->{headers};
839             }
840 0 0       0 if( exists( $opts->{payload} ) )
841             {
842 0         0 $payload = $opts->{payload};
843 0 0       0 if( ref( $payload ) eq 'HASH' )
844             {
845 0         0 local $@;
846             # try-catch
847             eval
848 0         0 {
849 0         0 $payload = $self->new_json->utf8->encode( $payload );
850             };
851 0 0       0 if( $@ )
852             {
853 0         0 return( $self->error( "Error encoding payload provided into JSON data: $@" ) );
854             }
855             }
856 0 0 0     0 if( exists( $opts->{method} ) &&
      0        
857             lc( $opts->{method} ) ne 'post' &&
858             lc( $opts->{method} ) ne 'put' )
859             {
860 0         0 return( $self->error( "The HTTP method specified is '", $opts->{method}, "', but you specified also a payload, which requires either POST or PUT." ) );
861             }
862             }
863 0 0       0 if( exists( $opts->{query} ) )
864             {
865 0         0 $query = $opts->{query};
866             }
867              
868 0         0 my $resp;
869             # If we are using a cache file for debugging purpose
870 0 0       0 if( my $cache_file = $self->cache_file )
    0          
    0          
    0          
871             {
872 0   0     0 my $data = $cache_file->load( binmode => ':raw' ) ||
873             return( $self->pass_error( $cache_file->error ) );
874 0   0     0 $resp = HTTP::Promise::Response->new( 200, 'OK', [
875             Connection => 'close',
876             Server => 'local_cache',
877             Content_Type => 'application/json; charset=utf-8',
878             Cache_Control => 'private',
879             Accept_Ranges => 'bytes',
880             Date => HTTP::Promise->httpize_datetime( $cache_file->last_modified->clone ),
881             # <https://developer.fastly.com/learning/concepts/shielding/#debugging>
882             X_Cache => 'MISS, MISS',
883             X_Cache_Hits => '0, 0',
884             ], $data ) || return( $self->pass_error( HTTP::Promise::Response->error ) );
885             }
886             elsif( defined( $req ) )
887             {
888 0 0       0 if( defined( $headers ) )
889             {
890             # $req->headers->header( @$headers );
891 0         0 for( my $i = 0; $i < scalar( @$headers ); $i += 2 )
892             {
893 0         0 $req->headers->replace( $headers->[$i] => $headers->[$i + 1] );
894             }
895             }
896            
897 0 0       0 if( defined( $query ) )
898             {
899 0 0 0     0 if( ref( $query ) eq 'HASH' || $self->_is_array( $query ) )
    0 0        
      0        
900             {
901 0         0 local $@;
902             # try-catch
903             eval
904 0         0 {
905 0         0 $req->uri->query_form( $query );
906             };
907 0 0       0 if( $@ )
908             {
909 0         0 return( $self->error( "Error while setting query form key-value pairs: $@" ) );
910             };
911             }
912             elsif( !ref( $query ) || ( ref( $query ) && overload::Method( $query => '""' ) ) )
913             {
914 0         0 $req->uri->query( "$query" );
915             }
916             }
917            
918 0 0       0 if( defined( $payload ) )
919             {
920 0 0       0 $req->content( $payload ) ||
921             return( $self->pass_error( $req->error ) );
922 0 0       0 unless( $req->headers->exists( 'Content-Type' ) )
923             {
924 0         0 $req->headers->header( Content_Type => 'application/json' );
925             }
926             }
927 0   0     0 $resp = $ua->request( $req ) ||
928             return( $self->pass_error( $ua->error ) );
929             }
930             elsif( lc( $meth ) eq 'get' )
931             {
932 0   0     0 $resp = $ua->get( $uri,
933             # Headers
934             ( defined( $headers ) ? @$headers : () ),
935             ( defined( $query ) ? ( Query => $query ) : () ),
936             ) || return( $self->pass_error( $ua->error ) );
937             }
938             elsif( lc( $meth ) eq 'post' )
939             {
940 0 0 0     0 if( defined( $payload ) &&
      0        
941             defined( $headers ) &&
942             !scalar( grep( /^Content[_-]Type$/i, @$headers ) ) )
943             {
944 0         0 push( @$headers, 'Content_Type', 'application/json' );
945             }
946 0   0     0 $resp = $ua->post( $uri,
947             # Headers
948             ( defined( $headers ) ? @$headers : () ),
949             # Payload
950             ( defined( $payload ) ? ( Content => $payload ) : () ),
951             ) || return( $self->pass_error( $ua->error ) );
952             }
953             else
954             {
955 0         0 return( $self->error( "Invalid method provided. The API only supports GET or POST." ) );
956             }
957              
958 0 0       0 if( $self->_is_a( $resp => 'HTTP::Promise::Exception' ) )
959             {
960 0         0 return( $self->pass_error( $resp ) );
961             }
962 0         0 $self->{http_request} = $resp->request;
963 0         0 $self->{http_response} = $resp;
964            
965 0         0 my $data;
966 0 0 0     0 if( $resp->is_success || $resp->is_redirect )
967             {
968 0         0 $self->message( 4, "Reponse headers are:\n", $resp->headers->as_string );
969 0         0 $self->message( 4, "Getting decoded content." );
970 0         0 my $content = $resp->decoded_content;
971 0 0       0 if( $resp->headers->content_is_json )
972             {
973 0         0 $self->message( 3, "Request successful, decoding its JSON content '", $content, "'" );
974             # decoded_content returns a scalar object, which we force into regular string, otherwise JSON complains it cannot parse it.
975 0         0 local $@;
976             # try-catch
977             $data = eval
978 0         0 {
979 0         0 $self->new_json->utf8->decode( "${content}" );
980             };
981 0 0       0 if( $@ )
982             {
983 0         0 return( $self->error({
984             code => 500,
985             message => "An error occurred trying to decode MetaCPAN API response payload: $@",
986             cause => { payload => $content },
987             }) );
988             }
989             }
990             else
991             {
992 0         0 $self->message( 4, "Content returned is not JSON -> ", $resp->headers->type );
993 0         0 $data = $content;
994             }
995            
996 0 0       0 if( defined( $postprocess ) )
997             {
998             # try-catch
999 0         0 local $@;
1000             $data = eval
1001 0         0 {
1002 0         0 $postprocess->( $data );
1003             };
1004 0 0       0 if( $@ )
1005             {
1006 0         0 return( $self->error( $@ ) );
1007             }
1008             }
1009              
1010 0         0 my $result;
1011 0 0       0 if( ref( $class ) eq 'CODE' )
1012             {
1013 0   0     0 $self->message( 4, "Class is actually a code callback, executing it with ", ( length( $data ) // 0 ), " bytes of data -> ", substr( $data, 0, 255 ) );
1014 0         0 local $@;
1015             # try-catch
1016             $result = eval
1017 0         0 {
1018 0         0 $class->( $data );
1019             };
1020 0 0 0     0 $self->message( 5, "Value returned from callback is: ", substr( ( $result // 'undef' ), 0, 255 ) . ( length( $result ) > 255 ? '...' : '' ) );
1021 0 0       0 if( $@ )
    0          
1022             {
1023 0         0 return( $self->error({
1024             code => 500,
1025             message => "An error occurred calling the callback to process data received from the MetaCPAN REST API for object $type: $@",
1026             }) );
1027             }
1028             elsif( !defined( $result ) )
1029             {
1030 0         0 return( $self->pass_error );
1031             }
1032             }
1033             else
1034             {
1035 0         0 $self->message( 4, "Loading class '$class'" );
1036 0 0       0 $self->_load_class( $class ) || return( $self->error );
1037 0         0 $self->message( 4, "Instantiating new object for class '$class'" );
1038             # $self->message( 5, "Option 'list_preprocess' provided? -> ", ( exists( $opts->{list_preprocess} ) ? 'yes' : 'no' ) );
1039             $result = $class->new(
1040             debug => $self->debug,
1041             (
1042             $class->isa( 'Net::API::CPAN::List' ) ? (
1043             api => $self,
1044             data => $data,
1045             request => $resp->request,
1046             type => $type,
1047             # Used by autocomplete
1048             ( ( exists( $opts->{list_preprocess} ) && ref( $opts->{list_preprocess} ) eq 'CODE' ) ? ( preprocess => $opts->{list_preprocess} ) : () ),
1049             ( ( exists( $opts->{list_postprocess} ) && ref( $opts->{list_postprocess} ) eq 'CODE' ) ? ( postprocess => $opts->{list_postprocess} ) : () ),
1050             ) : (),
1051             ),
1052             (
1053 0 0 0     0 ( exists( $opts->{args} ) && ref( $opts->{args} ) eq 'HASH' ) ? ( %{$opts->{args}} ) : (),
  0 0 0     0  
    0 0        
    0          
1054             ),
1055             );
1056 0 0       0 unless( $class->isa( 'Net::API::CPAN::List' ) )
1057             {
1058 0         0 $self->message( 4, "Applying API data to new object '", overload::StrVal( $result ), "'" );
1059 0 0       0 $result->apply( $data ) || return( $self->pass_error( $result->error ) );
1060             }
1061             }
1062 0         0 return( $result );
1063             }
1064             else
1065             {
1066 0         0 $self->messagef( 3, "Request failed with error %s", $resp->status );
1067 0 0       0 if( $resp->header( 'Content-Type' ) =~ m{text/html} )
    0          
1068             {
1069 0         0 return( $self->error({
1070             code => $resp->code->scalar,
1071             type => $resp->status->scalar,
1072             message => $resp->status->scalar
1073             }) );
1074             }
1075             elsif( $resp->headers->type =~ /json/i )
1076             {
1077 0         0 local $@;
1078 0         0 my $content = $resp->decoded_content;
1079             # try-catch
1080             eval
1081 0         0 {
1082 0         0 $data = $self->json->utf8->decode( "${content}" );
1083             };
1084 0 0       0 if( $@ )
1085             {
1086 0         0 return( $self->error({
1087             code => 500,
1088             message => "An error occurred trying to decode MetaCPAN API response payload: $@",
1089             cause => { payload => $content },
1090             }) );
1091             }
1092            
1093 0         0 my $ref = {};
1094 0 0 0     0 if( exists( $data->{error} ) &&
1095             defined( $data->{error} ) )
1096             {
1097 0 0 0     0 if( ref( $data->{error} ) eq 'HASH' &&
    0          
1098             exists( $data->{error}->{message} ) )
1099             {
1100 0         0 $ref->{message} = $data->{error}->{message};
1101             $ref->{code} = exists( $data->{error}->{code} )
1102             ? $data->{error}->{code}
1103 0 0       0 : $resp->code;
1104             }
1105             elsif( !ref( $data->{error} ) )
1106             {
1107 0         0 $ref->{message} = $data->{error};
1108 0         0 $ref->{code} = $resp->code;
1109             }
1110             }
1111             else
1112             {
1113 0         0 $ref = $data;
1114             }
1115 0         0 $ref->{cause} = { response => $resp, request => $resp->request };
1116 0         0 return( $self->error( $ref ) );
1117             }
1118             else
1119             {
1120 0         0 return( $self->error({
1121             code => $resp->code,
1122             message => $resp->status,
1123             }) );
1124             }
1125             }
1126             }
1127              
1128             sub file
1129             {
1130 0     0 1 0 my $self = shift( @_ );
1131 0 0       0 if( @_ )
1132             {
1133 0         0 my( $filter, $opts );
1134 0 0 0     0 if( scalar( @_ ) == 1 &&
1135             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
1136             {
1137 0         0 $filter = shift( @_ );
1138 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
1139             return( $self->pass_error( $filter->error ) );
1140 0         0 return( $self->fetch( file => {
1141             endpoint => "/file",
1142             class => $self->_object_type_to_class( 'list' ),
1143             args => {
1144             filter => $filter,
1145             },
1146             method => 'post',
1147             payload => $payload,
1148             }) );
1149             }
1150             else
1151             {
1152 0         0 $opts = $self->_get_args_as_hash( @_ );
1153 0 0 0     0 if( exists( $opts->{query} ) )
    0 0        
    0 0        
      0        
1154             {
1155             return( $self->fetch( favorite => {
1156             endpoint => "/file",
1157             class => $self->_object_type_to_class( 'list' ),
1158             query => {
1159             'q' => $opts->{query},
1160             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1161 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1162             }
1163             }) );
1164             }
1165             elsif( exists( $opts->{author} ) &&
1166             exists( $opts->{release} ) &&
1167             exists( $opts->{dir} ) )
1168             {
1169 0         0 foreach my $t ( qw( author release dir ) )
1170             {
1171 0 0       0 if( $self->_is_empty( $opts->{ $t } ) )
1172             {
1173 0         0 return( $self->error( "The $t value provided is empty." ) );
1174             }
1175             }
1176             return( $self->fetch( file => {
1177             endpoint => "/file/dir/" . join( '/', @$opts{qw( author release dir )} ),
1178             class => $self->_object_type_to_class( 'list' ),
1179             # We change the properties stat.mime and stat.size to an hash reference
1180             # stat { mime => 12345, size => 12345 }
1181             postprocess => sub
1182             {
1183 0     0   0 my $ref = shift( @_ );
1184 0 0 0     0 if( ref( $ref ) eq 'HASH' &&
      0        
1185             exists( $ref->{dir} ) &&
1186             ref( $ref->{dir} ) eq 'ARRAY' )
1187             {
1188 0         0 for( my $i = 0; $i < scalar( @{$ref->{dir}} ); $i++ )
  0         0  
1189             {
1190 0         0 my $this = $ref->{dir}->[$i];
1191 0 0 0     0 if( defined( $this ) &&
1192             ref( $this ) eq 'HASH' )
1193             {
1194 0         0 my @keys = grep( /^stat\.\w+$/, keys( %$this ) );
1195 0 0       0 if( scalar( @keys ) )
1196             {
1197 0         0 $this->{stat} = {};
1198 0         0 foreach my $f ( @keys )
1199             {
1200 0         0 my( $stat, $field ) = split( /\./, $f, 2 );
1201 0         0 $this->{stat}->{ $field } = CORE::delete( $this->{ $f } );
1202             }
1203             }
1204             }
1205 0         0 $ref->{dir}->[$i] = $this;
1206             }
1207             }
1208 0         0 return( $ref );
1209             },
1210 0         0 }) );
1211             }
1212             elsif( exists( $opts->{author} ) &&
1213             exists( $opts->{release} ) &&
1214             exists( $opts->{path} ) )
1215             {
1216 0         0 foreach my $t ( qw( author release path ) )
1217             {
1218 0 0       0 if( $self->_is_empty( $opts->{ $t } ) )
1219             {
1220 0         0 return( $self->error( "The $t value provided is empty." ) );
1221             }
1222             }
1223             return( $self->fetch( file => {
1224 0         0 endpoint => "/file/" . join( '/', @$opts{qw( author release path )} ),
1225             class => $self->_object_type_to_class( 'file' ),
1226             }) );
1227             }
1228             else
1229             {
1230 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
1231             }
1232             }
1233             }
1234             else
1235             {
1236 0         0 return( $self->fetch( 'file' => {
1237             endpoint => "/file",
1238             class => $self->_object_type_to_class( 'list' ),
1239             }) );
1240             }
1241             }
1242              
1243             sub first
1244             {
1245 0     0 1 0 my $self = shift( @_ );
1246 0         0 my $term = shift( @_ );
1247 0 0       0 return( $self->error( "No search term was provided." ) ) if( $self->_is_empty( $term ) );
1248             return( $self->fetch( 'search' => {
1249             endpoint => "/search/first",
1250             class => $self->_object_type_to_class( 'module' ),
1251             query => {
1252             'q' => $term,
1253             },
1254             postprocess => sub
1255             {
1256 0     0   0 my $ref = shift( @_ );
1257 0 0       0 if( exists( $ref->{ 'abstract.analyzed' } ) )
1258             {
1259 0         0 $ref->{abstract} = CORE::delete( $ref->{ 'abstract.analyzed' } );
1260             }
1261 0         0 return( $ref );
1262             },
1263 0         0 }) );
1264             }
1265              
1266 0     0 1 0 sub http_request { CORE::return( shift->_set_get_object_without_init( 'http_request', 'HTTP::Promise::Request', @_ ) ); }
1267              
1268 0     0 1 0 sub http_response { CORE::return( shift->_set_get_object_without_init( 'http_response', 'HTTP::Promise::Response', @_ ) ); }
1269              
1270             sub history
1271             {
1272 0     0 1 0 my $self = shift( @_ );
1273 0         0 my $opts = $self->_get_args_as_hash( @_ );
1274 0   0     0 my $type = $opts->{type} || return( $self->error( "No history type was provided." ) );
1275 0   0     0 my $path = $opts->{path} || return( $self->error( "No path was provided." ) );
1276 0 0       0 if( $type !~ /^(?:module|file|documentation)$/ )
1277             {
1278 0         0 return( $self->error( "Invalid type provided ($type). This can only be either 'module', 'file' or 'documentation'." ) );
1279             }
1280 0 0 0     0 if( $type eq 'module' && exists( $opts->{module} ) )
    0 0        
    0 0        
1281             {
1282             return( $self->fetch( 'module' => {
1283 0         0 endpoint => "/search/history/module/" . join( '/', @$opts{qw( module path )} ),
1284             class => $self->_object_type_to_class( 'list' ),
1285             }) );
1286             }
1287             elsif( $type eq 'file' && exists( $opts->{distribution} ) )
1288             {
1289             return( $self->fetch( 'file' => {
1290 0         0 endpoint => "/search/history/file/" . join( '/', @$opts{qw( distribution path )} ),
1291             class => $self->_object_type_to_class( 'list' ),
1292             }) );
1293             }
1294             elsif( $type eq 'documentation' && exists( $opts->{module} ) )
1295             {
1296             return( $self->fetch( 'file' => {
1297 0         0 endpoint => "/search/history/documentation/" . join( '/', @$opts{qw( module path )} ),
1298             class => $self->_object_type_to_class( 'list' ),
1299             }) );
1300             }
1301             else
1302             {
1303 0     0   0 return( $self->error( "Unknonw options provided -> ", sub{ $self->Module::Generic::dump( $opts ) } ) );
  0         0  
1304             }
1305             }
1306              
1307             sub json
1308             {
1309 0     0 1 0 my $self = shift( @_ );
1310 0 0       0 return( $self->{json} ) if( $self->{json} );
1311 0         0 $self->{json} = JSON->new->allow_nonref->allow_blessed->convert_blessed->relaxed;
1312 0         0 return( $self->{json} );
1313             }
1314              
1315             sub mirror
1316             {
1317 0     0 1 0 my $self = shift( @_ );
1318 0         0 return( $self->fetch( 'mirror' => {
1319             endpoint => "/mirror",
1320             class => $self->_object_type_to_class( 'list' ),
1321             }) );
1322             }
1323              
1324             sub module
1325             {
1326 0     0 1 0 my $self = shift( @_ );
1327 0 0       0 if( @_ )
1328             {
1329 0         0 my( $filter, $mod, $opts );
1330 0 0 0     0 if( scalar( @_ ) == 1 &&
1331             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
1332             {
1333 0         0 $filter = shift( @_ );
1334 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
1335             return( $self->pass_error( $filter->error ) );
1336 0         0 return( $self->fetch( module => {
1337             endpoint => "/module",
1338             class => $self->_object_type_to_class( 'list' ),
1339             args => {
1340             filter => $filter,
1341             },
1342             method => 'post',
1343             payload => $payload,
1344             }) );
1345             }
1346             else
1347             {
1348 0         0 $opts = $self->_get_args_as_hash( @_ );
1349 0 0 0     0 if( exists( $opts->{query} ) )
    0 0        
1350             {
1351             return( $self->fetch( module => {
1352             endpoint => "/module",
1353             class => $self->_object_type_to_class( 'list' ),
1354             query => {
1355             'q' => $opts->{query},
1356             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1357 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1358             }
1359             }) );
1360             }
1361             elsif( exists( $opts->{module} ) &&
1362             length( $opts->{module} // '' ) )
1363             {
1364 0         0 my $mod = $opts->{module};
1365 0         0 my $join;
1366 0 0       0 if( exists( $opts->{join} ) )
1367             {
1368             $join = $self->_is_array( $opts->{join} )
1369 0         0 ? [@{$opts->{join}}]
1370             : length( $opts->{join} // '' )
1371 0 0 0     0 ? [$opts->{join}]
    0          
1372             : [];
1373             }
1374             return( $self->fetch( module => {
1375             endpoint => "/module/${mod}",
1376             class => $self->_object_type_to_class( 'module' ),
1377             ( $join ? ( query => { join => $join } ) : () ),
1378             postprocess => sub
1379             {
1380 0     0   0 my $ref = shift( @_ );
1381 0 0       0 return( $ref ) if( !defined( $join ) );
1382 0 0 0     0 return( $ref ) if( !defined( $ref ) || ref( $ref ) ne 'HASH' );
1383 0         0 foreach my $t ( qw( author release ) )
1384             {
1385 0 0 0     0 if( exists( $ref->{ $t } ) &&
      0        
      0        
1386             ref( $ref->{ $t } ) eq 'HASH' &&
1387             exists( $ref->{ $t }->{_source} ) &&
1388             ref( $ref->{ $t }->{_source} ) eq 'HASH' )
1389             {
1390 0         0 $ref->{ $t } = $ref->{ $t }->{_source};
1391             }
1392             }
1393 0         0 return( $ref );
1394             },
1395 0 0       0 }) );
1396             }
1397             else
1398             {
1399 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
1400             }
1401             }
1402             }
1403             else
1404             {
1405 0         0 return( $self->fetch( 'module' => {
1406             endpoint => "/module",
1407             class => $self->_object_type_to_class( 'list' ),
1408             }) );
1409             }
1410             }
1411              
1412             sub new_filter
1413             {
1414 0     0 1 0 my $self = shift( @_ );
1415 0         0 my $opts = $self->_get_args_as_hash( @_ );
1416 0 0       0 $self->_load_class( 'Net::API::CPAN::Filter' ) || return( $self->pass_error );
1417 0   0     0 my $filter = Net::API::CPAN::Filter->new( %$opts, debug => $self->debug ) ||
1418             return( $self->pass_error( Net::API::CPAN::Filter->error ) );
1419 0         0 return( $filter );
1420             }
1421              
1422             sub package
1423             {
1424 0     0 1 0 my $self = shift( @_ );
1425 0 0       0 if( @_ )
1426             {
1427 0         0 my( $filter, $mod, $opts );
1428 0 0 0     0 if( scalar( @_ ) == 1 &&
    0 0        
      0        
1429             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
1430             {
1431 0         0 $filter = shift( @_ );
1432 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
1433             return( $self->pass_error( $filter->error ) );
1434 0         0 return( $self->fetch( package => {
1435             endpoint => "/package",
1436             class => $self->_object_type_to_class( 'list' ),
1437             args => {
1438             filter => $filter,
1439             },
1440             method => 'post',
1441             payload => $payload,
1442             }) );
1443             }
1444             elsif( scalar( @_ ) == 1 &&
1445             ( !ref( $_[0] ) || ( ref( $_[0] ) && overload::Method( $_[0] => '""' ) ) ) )
1446             {
1447 0         0 $mod = shift( @_ );
1448 0         0 return( $self->fetch( package => {
1449             endpoint => "/package/${mod}",
1450             class => $self->_object_type_to_class( 'package' ),
1451             }) );
1452             }
1453             else
1454             {
1455 0         0 $opts = $self->_get_args_as_hash( @_ );
1456 0 0       0 if( exists( $opts->{query} ) )
    0          
1457             {
1458             return( $self->fetch( package => {
1459             endpoint => "/package",
1460             class => $self->_object_type_to_class( 'list' ),
1461             query => {
1462             'q' => $opts->{query},
1463             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1464             ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
1465             },
1466             # Issue No 1136: some dataset contains the property version with string value of 'undef' in JSON instead of null
1467             # we check it and convert it here until this is fixed.
1468             # <https://github.com/metacpan/metacpan-api/issues/1136>
1469             postprocess => sub
1470             {
1471 0     0   0 my $ref = shift( @_ );
1472 0 0 0     0 if( ref( $ref ) eq 'HASH' &&
      0        
      0        
      0        
1473             exists( $ref->{hits} ) &&
1474             ref( $ref->{hits} ) eq 'HASH' &&
1475             exists( $ref->{hits}->{hits} ) &&
1476             ref( $ref->{hits}->{hits} ) eq 'ARRAY' )
1477             {
1478 0         0 for( my $i = 0; $i < scalar( @{$ref->{hits}->{hits}} ); $i++ )
  0         0  
1479             {
1480 0         0 my $this = $ref->{hits}->{hits}->[$i];
1481 0 0 0     0 if( defined( $this ) &&
      0        
      0        
      0        
      0        
      0        
1482             ref( $this ) eq 'HASH' &&
1483             exists( $this->{_source} ) &&
1484             ref( $this->{_source} ) eq 'HASH' &&
1485             exists( $this->{_source}->{version} ) &&
1486             defined( $this->{_source}->{version} ) &&
1487             $this->{_source}->{version} eq 'undef' )
1488             {
1489 0         0 $this->{_source}->{version} = undef;
1490 0         0 $ref->{hits}->{hits}->[$i] = $this;
1491             }
1492             }
1493             }
1494 0         0 return( $ref );
1495             },
1496 0 0       0 }) );
    0          
1497             }
1498             elsif( exists( $opts->{distribution} ) )
1499             {
1500 0 0       0 return( $self->error( "Value provided for distribution is empty." ) ) if( $self->_is_empty( $opts->{distribution} ) );
1501             return( $self->fetch( package => {
1502             endpoint => "/package/modules/" . $opts->{distribution},
1503             class => sub
1504             {
1505 0     0   0 my $ref = shift( @_ );
1506 0 0 0     0 if( ref( $ref ) ne 'HASH' ||
    0 0        
1507             ( ref( $ref ) eq 'HASH' && !exists( $ref->{modules} ) ) )
1508             {
1509 0         0 return( $self->error( "No \"modules\" property found in data returned by MetaCPAN REST API." ) );
1510             }
1511             elsif( ref( $ref->{modules} ) ne 'ARRAY' )
1512             {
1513 0         0 return( $self->error( "The \"modules\" property returned by the MetaCPAN REST API is not an array reference." ) );
1514             }
1515 0         0 return( $self->new_array( $ref->{modules} ) );
1516             },
1517 0         0 }) );
1518             }
1519             else
1520             {
1521 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
1522             }
1523             }
1524             }
1525             else
1526             {
1527 0         0 return( $self->fetch( 'package' => {
1528             endpoint => "/package",
1529             class => $self->_object_type_to_class( 'list' ),
1530             }) );
1531             }
1532             }
1533              
1534             sub permission
1535             {
1536 0     0 1 0 my $self = shift( @_ );
1537 0 0       0 if( @_ )
1538             {
1539 0         0 my( $filter, $opts );
1540 0 0 0     0 if( scalar( @_ ) == 1 &&
1541             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
1542             {
1543 0         0 $filter = shift( @_ );
1544 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
1545             return( $self->pass_error( $filter->error ) );
1546 0         0 return( $self->fetch( permission => {
1547             endpoint => "/permission",
1548             class => $self->_object_type_to_class( 'list' ),
1549             args => {
1550             filter => $filter,
1551             },
1552             method => 'post',
1553             payload => $payload,
1554             }) );
1555             }
1556             else
1557             {
1558 0         0 $opts = $self->_get_args_as_hash( @_ );
1559 0 0       0 if( exists( $opts->{query} ) )
    0          
    0          
1560             {
1561             return( $self->fetch( permission => {
1562             endpoint => "/permission",
1563             class => $self->_object_type_to_class( 'list' ),
1564             query => {
1565             'q' => $opts->{query},
1566             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1567 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1568             }
1569             }) );
1570             }
1571             elsif( exists( $opts->{author} ) )
1572             {
1573 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1574             return( $self->fetch( permission => {
1575             endpoint => "/permission/by_author/" . $opts->{author},
1576             class => $self->_object_type_to_class( 'list' ),
1577             query => {
1578             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1579 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1580             }
1581             }) );
1582             }
1583             elsif( exists( $opts->{module} ) )
1584             {
1585             # This endpoint /permission/by_module can take a query string as used below,
1586             # but can also take a module in its path, such as:
1587             # /permission/by_module/HTTP::Message
1588             # which returns the same data structure
1589             # Since it is identical to using q query string for 1 or more module, we do not use it.
1590 0 0       0 if( $self->_is_array( $opts->{module} ) )
1591             {
1592             return( $self->fetch( 'permission' => {
1593             endpoint => "/permission/by_module",
1594             class => $self->_object_type_to_class( 'list' ),
1595             query => {
1596 0         0 module => [@{$opts->{module}}],
  0         0  
1597             }
1598             }) );
1599             }
1600             else
1601             {
1602 0 0       0 return( $self->error( "Value provided for module is empty." ) ) if( $self->_is_empty( $opts->{module} ) );
1603             return( $self->fetch( 'permission' => {
1604             endpoint => "/permission/" . $opts->{module},
1605 0         0 class => $self->_object_type_to_class( 'permission' ),
1606             }) );
1607             }
1608             }
1609             else
1610             {
1611 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
1612             }
1613             }
1614             }
1615             else
1616             {
1617 0         0 return( $self->fetch( 'permission' => {
1618             endpoint => "/permission",
1619             class => $self->_object_type_to_class( 'list' ),
1620             }) );
1621             }
1622             }
1623              
1624             sub pod
1625             {
1626 0     0 1 0 my $self = shift( @_ );
1627 0         0 my $opts = $self->_get_args_as_hash( @_ );
1628 0 0       0 if( !scalar( keys( %$opts ) ) )
1629             {
1630 0         0 return( $self->error( "No option was specified for pod." ) );
1631             }
1632 0 0 0     0 if( exists( $opts->{author} ) &&
    0 0        
    0          
1633             exists( $opts->{release} ) &&
1634             exists( $opts->{path} ) )
1635             {
1636 0         0 foreach my $t ( qw( author release path ) )
1637             {
1638 0 0       0 if( $self->_is_empty( $opts->{ $t } ) )
1639             {
1640 0         0 return( $self->error( "The $t value provided is empty." ) );
1641             }
1642             }
1643 0         0 $opts->{author} = uc( $opts->{author} );
1644             return( $self->fetch( 'pod' => {
1645             endpoint => "/pod/" . join( '/', @$opts{qw( author release path )} ),
1646 0     0   0 class => sub{$_[0]},
1647             (
1648             exists( $opts->{accept} ) ? ( headers => [Accept => $opts->{accept}] ) : ()
1649             ),
1650             # Because MetaCPAN API does not recognise the Accept header, even though it is coded that way,
1651             # we must use this query string to be more explicit
1652             (
1653 0 0       0 exists( $opts->{accept} ) ? ( query => { content_type => $opts->{accept} } ) : ()
    0          
1654             ),
1655             }) );
1656             }
1657             elsif( exists( $opts->{module} ) )
1658             {
1659 0 0       0 if( $self->_is_empty( $opts->{module} ) )
    0          
1660             {
1661 0         0 return( $self->error( "Value provided for module is empty." ) );
1662             }
1663             elsif( !$self->_is_module( $opts->{module} ) )
1664             {
1665 0         0 return( $self->error( "Value provided for module ($opts->{module}) does not look like a module." ) );
1666             }
1667             return( $self->fetch( 'pod' => {
1668             endpoint => "/pod/" . $opts->{module},
1669 0     0   0 class => sub{$_[0]},
1670             (
1671             exists( $opts->{accept} ) ? ( headers => [Accept => $opts->{accept}] ) : ()
1672             ),
1673             # Because MetaCPAN API does not recognise the Accept header, even though it is coded that way,
1674             # we must use this query string to be more explicit
1675             (
1676 0 0       0 exists( $opts->{accept} ) ? ( query => { content_type => $opts->{accept} } ) : ()
    0          
1677             ),
1678             }) );
1679             }
1680             elsif( exists( $opts->{render} ) )
1681             {
1682 0 0       0 if( $self->_is_empty( $opts->{render} ) )
1683             {
1684 0         0 return( $self->error( "Value provided for render is empty." ) );
1685             }
1686             return( $self->fetch( 'pod' => {
1687             endpoint => "/pod_render",
1688 0     0   0 class => sub{$_[0]},
1689             query => {
1690             pod => $opts->{render},
1691             },
1692 0         0 }) );
1693             }
1694             else
1695             {
1696 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
1697             }
1698             }
1699              
1700             sub rating
1701             {
1702 0     0 1 0 my $self = shift( @_ );
1703 0 0       0 if( @_ )
1704             {
1705 0         0 my( $filter, $opts );
1706 0 0 0     0 if( scalar( @_ ) == 1 &&
1707             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
1708             {
1709 0         0 $filter = shift( @_ );
1710 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
1711             return( $self->pass_error( $filter->error ) );
1712 0         0 return( $self->fetch( rating => {
1713             endpoint => "/rating",
1714             class => $self->_object_type_to_class( 'list' ),
1715             args => {
1716             filter => $filter,
1717             },
1718             method => 'post',
1719             payload => $payload,
1720             }) );
1721             }
1722             else
1723             {
1724 0         0 $opts = $self->_get_args_as_hash( @_ );
1725 0 0       0 if( exists( $opts->{query} ) )
    0          
    0          
1726             {
1727             return( $self->fetch( rating => {
1728             endpoint => "/rating",
1729             class => $self->_object_type_to_class( 'list' ),
1730             query => {
1731             'q' => $opts->{query},
1732             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1733 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1734             }
1735             }) );
1736             }
1737             elsif( exists( $opts->{distribution} ) )
1738             {
1739 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1740             return( $self->fetch( rating => {
1741             endpoint => "/rating/by_author",
1742             class => $self->_object_type_to_class( 'list' ),
1743             query => {
1744             'q' => $opts->{author},
1745             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1746 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1747             }
1748             }) );
1749             }
1750             elsif( exists( $opts->{distribution} ) )
1751             {
1752 0 0       0 my $dist = $self->_is_array( $opts->{distribution} ) ? [@{$opts->{distribution}}] : [$opts->{distribution}];
  0         0  
1753 0         0 return( $self->fetch( 'rating' => {
1754             endpoint => "/rating/by_distributions",
1755             class => $self->_object_type_to_class( 'list' ),
1756             query => {
1757             module => $dist,
1758             }
1759             }) );
1760             }
1761             else
1762             {
1763 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
1764             }
1765             }
1766             }
1767             else
1768             {
1769 0         0 return( $self->fetch( 'rating' => {
1770             endpoint => "/rating",
1771             class => $self->_object_type_to_class( 'list' ),
1772             }) );
1773             }
1774             }
1775              
1776             sub release
1777             {
1778 0     0 1 0 my $self = shift( @_ );
1779 0 0       0 if( @_ )
1780             {
1781 0         0 my( $filter, $opts );
1782 0 0 0     0 if( scalar( @_ ) == 1 &&
1783             $self->_is_a( $_[0] => 'Net::API::CPAN::Filter' ) )
1784             {
1785 0         0 $filter = shift( @_ );
1786 0   0     0 my $payload = $filter->as_json( encoding => 'utf8' ) ||
1787             return( $self->pass_error( $filter->error ) );
1788 0         0 return( $self->fetch( release => {
1789             endpoint => "/release",
1790             class => $self->_object_type_to_class( 'list' ),
1791             args => {
1792             filter => $filter,
1793             },
1794             method => 'post',
1795             payload => $payload,
1796             }) );
1797             }
1798             else
1799             {
1800 0         0 $opts = $self->_get_args_as_hash( @_ );
1801             # NOTE: release -> query
1802 0 0 0     0 if( exists( $opts->{query} ) )
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
1803             {
1804             return( $self->fetch( release => {
1805             endpoint => "/release",
1806             class => $self->_object_type_to_class( 'list' ),
1807             query => {
1808             'q' => $opts->{query},
1809             ( exists( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
1810 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1811             }
1812             }) );
1813             }
1814             # NOTE: release -> all AUTHOR
1815             elsif( exists( $opts->{all} ) )
1816             {
1817 0 0       0 return( $self->error( "Value provided for all is empty." ) ) if( $self->_is_empty( $opts->{all} ) );
1818 0         0 $opts->{all} = uc( $opts->{all} );
1819             return( $self->fetch( release => {
1820             endpoint => "/release/all_by_author/" . $opts->{all},
1821             class => $self->_object_type_to_class( 'list' ),
1822             args => { page_type => 'page' },
1823             # Bug No 1126, the parameters page and size are inverted. It was fixed, but the fix was reverted on 2023-09-08
1824             # <https://github.com/metacpan/metacpan-api/issues/1126>
1825             # <https://github.com/metacpan/metacpan-api/actions/runs/6115139953>
1826             # Until this is finally fixed, we need to invert the parameters, weirdly enough
1827             query => {
1828             ( exists( $opts->{page} ) ? ( page => $opts->{size} ) : () ),
1829 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{page} ) : () ),
    0          
1830             }
1831             }) );
1832             }
1833             # NOTE: release -> author, release, contributors
1834             elsif( exists( $opts->{author} ) &&
1835             exists( $opts->{release} ) &&
1836             exists( $opts->{contributors} ) )
1837             {
1838 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1839 0 0       0 return( $self->error( "Value provided for release is empty." ) ) if( $self->_is_empty( $opts->{release} ) );
1840 0         0 $opts->{author} = uc( $opts->{author} );
1841             return( $self->fetch( 'release' => {
1842 0         0 endpoint => "/release/contributors/" . join( '/', @$opts{qw( author release )} ),
1843             class => $self->_object_type_to_class( 'list' ),
1844             }) );
1845             }
1846             # NOTE: release -> author, release, files
1847             elsif( exists( $opts->{author} ) &&
1848             exists( $opts->{release} ) &&
1849             exists( $opts->{files} ) )
1850             {
1851 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1852 0 0       0 return( $self->error( "Value provided for release is empty." ) ) if( $self->_is_empty( $opts->{release} ) );
1853 0         0 $opts->{author} = uc( $opts->{author} );
1854             return( $self->fetch( 'release' => {
1855             endpoint => "/release/files_by_category/" . join( '/', @$opts{qw( author release )} ),
1856 0     0   0 class => sub{ $_[0] },
1857 0         0 }) );
1858             }
1859             # NOTE: release -> author, release, modules
1860             elsif( exists( $opts->{author} ) &&
1861             exists( $opts->{release} ) &&
1862             exists( $opts->{modules} ) )
1863             {
1864 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1865 0 0       0 return( $self->error( "Value provided for release is empty." ) ) if( $self->_is_empty( $opts->{release} ) );
1866 0         0 $opts->{author} = uc( $opts->{author} );
1867             return( $self->fetch( 'file' => {
1868 0         0 endpoint => "/release/modules/" . join( '/', @$opts{qw( author release )} ),
1869             class => $self->_object_type_to_class( 'list' ),
1870             }) );
1871             }
1872             # NOTE: release -> author, release, interesting_files
1873             elsif( exists( $opts->{author} ) &&
1874             exists( $opts->{release} ) &&
1875             ( exists( $opts->{interesting_files} ) || exists( $opts->{interesting} ) ) )
1876             {
1877 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1878 0 0       0 return( $self->error( "Value provided for release is empty." ) ) if( $self->_is_empty( $opts->{release} ) );
1879 0         0 $opts->{author} = uc( $opts->{author} );
1880             return( $self->fetch( 'file' => {
1881 0         0 endpoint => "/release/interesting_files/" . join( '/', @$opts{qw( author release )} ),
1882             class => $self->_object_type_to_class( 'list' ),
1883             }) );
1884             }
1885             # NOTE: release -> author, release
1886             elsif( exists( $opts->{author} ) &&
1887             exists( $opts->{release} ) )
1888             {
1889 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1890 0 0       0 return( $self->error( "Value provided for release is empty." ) ) if( $self->_is_empty( $opts->{release} ) );
1891 0         0 $opts->{author} = uc( $opts->{author} );
1892             return( $self->fetch( 'release' => {
1893             endpoint => "/release/" . join( '/', @$opts{qw( author release )} ),
1894             class => $self->_object_type_to_class( 'release' ),
1895             postprocess => sub
1896             {
1897 0     0   0 my $ref = shift( @_ );
1898 0 0 0     0 if( exists( $ref->{release} ) &&
      0        
1899             defined( $ref->{release} ) &&
1900             ref( $ref->{release} ) eq 'HASH' )
1901             {
1902 0         0 return( $ref->{release} );
1903             }
1904 0         0 return( $ref );
1905             },
1906 0         0 }) );
1907             }
1908             # NOTE: release -> author, latest
1909             elsif( exists( $opts->{author} ) &&
1910             exists( $opts->{latest} ) )
1911             {
1912 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1913 0         0 $opts->{author} = uc( $opts->{author} );
1914             return( $self->fetch( 'release' => {
1915             endpoint => "/release/latest_by_author/" . $opts->{author},
1916 0         0 class => $self->_object_type_to_class( 'list' ),
1917             }) );
1918             }
1919             # NOTE: release -> distribution, latest
1920             elsif( exists( $opts->{distribution} ) &&
1921             exists( $opts->{latest} ) )
1922             {
1923 0 0       0 return( $self->error( "Value provided for distribution is empty." ) ) if( $self->_is_empty( $opts->{distribution} ) );
1924             return( $self->fetch( 'release' => {
1925             endpoint => "/release/latest_by_distribution/" . $opts->{distribution},
1926             class => $self->_object_type_to_class( 'release' ),
1927             postprocess => sub
1928             {
1929 0     0   0 my $ref = shift( @_ );
1930 0 0 0     0 if( exists( $ref->{release} ) &&
      0        
1931             defined( $ref->{release} ) &&
1932             ref( $ref->{release} ) eq 'HASH' )
1933             {
1934 0         0 return( $ref->{release} );
1935             }
1936 0         0 return( $ref );
1937             },
1938 0         0 }) );
1939             }
1940             # NOTE: release -> distribution, versions
1941             elsif( exists( $opts->{distribution} ) &&
1942             exists( $opts->{versions} ) )
1943             {
1944             # return( $self->error( "Value provided for versions is empty." ) ) if( $self->_is_empty( $opts->{versions} ) );
1945 0         0 my $query;
1946 0 0 0     0 if( exists( $opts->{plain} ) &&
1947             !$self->_is_empty( $opts->{plain} ) )
1948             {
1949 0         0 $query = { plain => $opts->{plain} };
1950             }
1951            
1952 0 0 0     0 if( ( $self->_is_array( $opts->{versions} ) && scalar( @{$opts->{versions}} ) ) ||
  0   0     0  
      0        
1953             ( defined( $opts->{versions} ) && length( "$opts->{versions}" ) ) )
1954             {
1955 0 0       0 if( $self->_is_array( $opts->{versions} ) )
1956             {
1957 0   0     0 $query //= {};
1958 0         0 $query->{versions} = join( ',', @{$opts->{versions}} );
  0         0  
1959             }
1960             else
1961             {
1962 0   0     0 $query //= {};
1963 0         0 $query->{versions} = $opts->{versions};
1964             }
1965             }
1966            
1967             return( $self->fetch( 'release' => {
1968             endpoint => "/release/versions/" . $opts->{distribution},
1969             (
1970             defined( $query ) ? ( query => $query ) : (),
1971             ),
1972             # If the user wants the plain text data, we return it as-is, otherwise, we return a list object.
1973 0 0   0   0 class => $opts->{plain} ? sub{$_[0]} : $self->_object_type_to_class( 'list' ),
  0 0       0  
1974             }) );
1975             }
1976             # NOTE: release -> author
1977             elsif( exists( $opts->{author} ) )
1978             {
1979 0 0       0 return( $self->error( "Value provided for author is empty." ) ) if( $self->_is_empty( $opts->{author} ) );
1980 0         0 $opts->{author} = uc( $opts->{author} );
1981             return( $self->fetch( release => {
1982             endpoint => "/release/by_author/" . $opts->{author},
1983             class => $self->_object_type_to_class( 'list' ),
1984             args => { page_type => 'page' },
1985             query => {
1986             ( exists( $opts->{page} ) ? ( page => $opts->{page} ) : () ),
1987 0 0       0 ( exists( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
1988             }
1989             }) );
1990             }
1991             # NOTE: release -> distribution
1992             elsif( exists( $opts->{distribution} ) )
1993             {
1994 0 0       0 return( $self->error( "Value provided for distribution is empty." ) ) if( $self->_is_empty( $opts->{distribution} ) );
1995 0         0 $opts->{author} = uc( $opts->{author} );
1996             return( $self->fetch( 'release' => {
1997             endpoint => "/release/" . $opts->{distribution},
1998 0         0 class => $self->_object_type_to_class( 'release' ),
1999             }) );
2000             }
2001             # NOTE: release -> recent
2002             elsif( exists( $opts->{recent} ) )
2003             {
2004             return( $self->fetch( 'release' => {
2005             endpoint => "/release/recent",
2006             class => $self->_object_type_to_class( 'list' ),
2007             args => { page_type => 'page' },
2008             query => {
2009             ( exists( $opts->{page} ) ? ( page => $opts->{page} ) : () ),
2010 0 0       0 ( exists( $opts->{size} ) ? ( page_size => $opts->{size} ) : () ),
    0          
2011             }
2012             }) );
2013             }
2014             else
2015             {
2016 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
2017             }
2018             }
2019             }
2020             else
2021             {
2022 0         0 return( $self->fetch( 'release' => {
2023             endpoint => "/release",
2024             class => $self->_object_type_to_class( 'list' ),
2025             }) );
2026             }
2027             }
2028              
2029             sub reverse
2030             {
2031 0     0 1 0 my $self = shift( @_ );
2032 0         0 my $opts = $self->_get_args_as_hash( @_ );
2033 0 0 0     0 if( exists( $opts->{distribution} ) && length( $opts->{distribution} // '' ) )
    0 0        
      0        
      0        
2034             {
2035             return( $self->fetch( 'release' => {
2036             endpoint => "/reverse_dependencies/dist/" . $opts->{distribution},
2037             class => $self->_object_type_to_class( 'list' ),
2038             query => {
2039             ( exists( $opts->{page} ) ? ( page => $opts->{page} ) : () ),
2040             # How many elements returned per page
2041             ( exists( $opts->{size} ) ? ( page_size => $opts->{size} ) : () ),
2042 0 0       0 ( exists( $opts->{sort} ) ? ( sort => $opts->{sort} ) : () ),
    0          
    0          
2043             },
2044             }) );
2045             }
2046             elsif( exists( $opts->{module} ) && length( $opts->{module} // '' ) )
2047             {
2048             return( $self->fetch( 'release' => {
2049             endpoint => "/reverse_dependencies/module/" . $opts->{module},
2050             class => $self->_object_type_to_class( 'list' ),
2051             query => {
2052             ( exists( $opts->{page} ) ? ( page => $opts->{page} ) : () ),
2053             # How many elements returned per page
2054             ( exists( $opts->{size} ) ? ( page_size => $opts->{size} ) : () ),
2055 0 0       0 ( exists( $opts->{sort} ) ? ( sort => $opts->{sort} ) : () ),
    0          
    0          
2056             },
2057             }) );
2058             }
2059             else
2060             {
2061 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
2062             }
2063             }
2064              
2065             {
2066 3     3   27 no warnings 'once';
  3         7  
  3         3661  
2067             *reverse_dependencies = \&reverse;
2068             }
2069              
2070             sub search
2071             {
2072 0     0 1 0 my $self = shift( @_ );
2073 0         0 my $opts = $self->_get_args_as_hash( @_ );
2074 0   0     0 my $type = delete( $opts->{type} ) ||
2075             return( $self->error( "No API endpoint search type was provided." ) );
2076 0 0       0 return( $self->error( "API endpoint type \"${type}\" contains illegal characters. Only alphanumerical characters are supported." ) ) if( $type !~ /^[a-zA-Z]\w+$/ );
2077             # my $query = delete( $opts->{query} ) ||
2078             # return( $self->error( "No search query was provided." ) );
2079 0   0     0 my $filter = $self->new_filter( $opts ) ||
2080             return( $self->pass_error );
2081             # $filter->apply( $opts );
2082             return( $self->fetch( $type => {
2083             endpoint => "/${type}/_search",
2084             class => $self->_object_type_to_class( 'list' ),
2085             payload => $filter->as_json( encoding => 'utf-8' ),
2086             method => 'post',
2087             headers => [
2088             Content_Type => 'application/json',
2089 0 0       0 ( exists( $opts->{accept} ) ? ( Accept => $opts->{accept} ) : () ),
2090             ],
2091             }) );
2092             }
2093              
2094             sub source
2095             {
2096 0     0 1 0 my $self = shift( @_ );
2097 0         0 my $opts = $self->_get_args_as_hash( @_ );
2098 0 0 0     0 if( exists( $opts->{author} ) &&
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
2099             length( $opts->{author} // '' ) &&
2100             exists( $opts->{release} ) &&
2101             length( $opts->{release} // '' ) &&
2102             exists( $opts->{path} ) &&
2103             length( $opts->{path} // '' ) )
2104             {
2105             # Returns a string
2106             return( $self->fetch( 'source' => {
2107             endpoint => "/source/" . join( '/', @$opts{qw( author release path )} ),
2108             # Returns data as-is
2109 0     0   0 class => sub{$_[0]},
2110 0         0 }) );
2111             }
2112             elsif( exists( $opts->{module} ) &&
2113             length( $opts->{module} // '' ) )
2114             {
2115             # Returns a string
2116             return( $self->fetch( 'source' => {
2117             endpoint => "/source/" . $opts->{module},
2118             # Returns data as-is
2119 0     0   0 class => sub{$_[0]},
2120 0         0 }) );
2121             }
2122             else
2123             {
2124 0         0 return( $self->error( "Unknown option properties provided: ", join( ', ', sort( keys( %$opts ) ) ) ) );
2125             }
2126             }
2127              
2128             sub suggest
2129             {
2130 0     0 1 0 my $self = shift( @_ );
2131 0         0 my $opts = $self->_get_args_as_hash( @_ );
2132 0 0       0 return( $self->error( "No search term was provided." ) ) if( $self->_is_empty( $opts->{query} ) );
2133             return( $self->fetch( 'release_suggest' => {
2134             endpoint => "/search/autocomplete/suggest",
2135             class => $self->_object_type_to_class( 'list' ),
2136             query => {
2137             'q' => $opts->{query},
2138             },
2139 0         0 }) );
2140             }
2141              
2142             sub top_uploaders
2143             {
2144 0     0 1 0 my $self = shift( @_ );
2145 0         0 my $opts = $self->_get_args_as_hash( @_ );
2146             # Possible values are 'all', 'weekly', 'monthly' or 'yearly'
2147 0         0 my $query;
2148 0 0 0     0 if( exists( $opts->{range} ) &&
      0        
2149             !$self->_is_empty( $opts->{range} ) &&
2150             $opts->{range} =~ /^\w+$/ )
2151             {
2152 0         0 $query = { range => $opts->{range} };
2153             }
2154 0 0 0     0 if( exists( $opts->{size} ) &&
      0        
2155             !$self->_is_empty( $opts->{size} ) &&
2156             $opts->{size} =~ /^\d+$/ )
2157             {
2158 0   0     0 $query //= {};
2159 0         0 $query->{size} = $opts->{size};
2160             }
2161             return( $self->fetch( 'release' => {
2162             endpoint => "/release/top_uploaders",
2163             (
2164             defined( $query ) ? ( query => $query ) : (),
2165             ),
2166             class => sub
2167             {
2168 0     0   0 my $ref = shift( @_ );
2169 0 0 0     0 if( exists( $ref->{counts} ) &&
      0        
2170             defined( $ref->{counts} ) &&
2171             ref( $ref->{counts} ) eq 'HASH' )
2172             {
2173 0         0 return( $self->new_hash( $ref->{counts} ) );
2174             }
2175 0         0 return( $ref );
2176             },
2177 0 0       0 }) );
2178             }
2179              
2180 1     1 1 1495 sub ua { return( shift->_set_get_object_without_init( 'ua', 'HTTP::Promise', @_ ) ); }
2181              
2182             sub web
2183             {
2184 0     0 1 0 my $self = shift( @_ );
2185 0         0 my $opts = $self->_get_args_as_hash( @_ );
2186 0 0       0 return( $self->error( "No search term was provided." ) ) if( $self->_is_empty( $opts->{query} ) );
2187             return( $self->fetch( 'list_web' => {
2188             endpoint => "/search/web",
2189             # The data structure looks like a regular list, but is non-standard.
2190             # We use the special class list_web (Net::API::CPAN::List::Web)
2191             class => $self->_object_type_to_class( 'list' ),
2192             query => {
2193             'q' => $opts->{query},
2194             ( $opts->{collapsed} ? ( collapsed => $opts->{collapsed} ) : () ),
2195             ( length( $opts->{from} ) ? ( from => $opts->{from} ) : () ),
2196 0 0       0 ( length( $opts->{size} ) ? ( size => $opts->{size} ) : () ),
    0          
    0          
2197             },
2198             }) );
2199             }
2200              
2201 0     0   0 sub _is_module { return( $_[1] =~ /^$MODULE_RE$/ ); }
2202              
2203             sub _object_type_to_class
2204             {
2205 1     1   29 my $self = shift( @_ );
2206 1   50     7 my $type = shift( @_ ) ||
2207             return( $self->error( "No object type was provided to derive its module name" ) );
2208 1         3 my $class = '';
2209 1 50       6 if( exists( $TYPE2CLASS->{ $type } ) )
    0          
2210             {
2211 1         6 return( $TYPE2CLASS->{ $type } );
2212 0           $class = 'Net::API::CPAN::' . join( '', map( ucfirst( lc( $_ ) ), split( /_/, $type ) ) );
2213             }
2214             elsif( $type =~ /^$MODULE_RE$/ )
2215             {
2216             # $type provided is actually already a package name
2217 0           $class = $type;
2218             }
2219             # returns either the class, or if nothing found an empty, but defined, string.
2220 0           return( $class );
2221             }
2222              
2223             sub _query_fields
2224             {
2225 0     0     my $self = shift( @_ );
2226 0           my $opts = $self->_get_args_as_hash( @_ );
2227 0 0 0       if( !exists( $opts->{fields} ) || !length( $opts->{fields} // '' ) )
      0        
2228             {
2229 0           return( '' );
2230             }
2231 0           my $fields = $opts->{fields};
2232 0           my $clean = $self->new_array;
2233 0 0 0       if( $self->_is_array( $fields ) )
    0 0        
      0        
2234             {
2235 0           for( @$fields )
2236             {
2237 0 0 0       if( !ref( $_ ) || ( ref( $_ ) && $self->_is_scalar( $_ ) && $self->_can_overload( $_ => '""' ) ) )
      0        
      0        
2238             {
2239 0           $clean->push( "$_" );
2240             }
2241             }
2242             }
2243             elsif( !ref( $fields ) || ( ref( $fields ) && $self->_is_scalar( $fields ) && $self->_can_overload( $fields => '""' ) ) )
2244             {
2245 0           $clean->push( "$fields" );
2246             }
2247 0 0         return( '' ) if( $clean->is_empty );
2248             # We return an hash that URI will use to properly encode and add as a query string
2249 0           return( { fields => $clean->join( ',' )->scalar } );
2250             }
2251              
2252             1;
2253             # NOTE: POD
2254             __END__
2255              
2256             =encoding utf-8
2257              
2258             =head1 NAME
2259              
2260             Net::API::CPAN - Meta CPAN API
2261              
2262             =head1 SYNOPSIS
2263              
2264             use Net::API::CPAN;
2265             my $cpan = Net::API::CPAN->new(
2266             api_version => 1,
2267             ua => HTTP::Promise->new( %options ),
2268             debug => 4,
2269             ) || die( Net::API::CPAN->error, "\n" );
2270             $cpan->api_uri( 'https://api.example.org' );
2271             my $uri = $cpan->api_uri;
2272             $cpan->api_version(1);
2273             my $version = $cpan->api_version;
2274              
2275             =head1 VERSION
2276              
2277             v0.1.0
2278              
2279             =head1 DESCRIPTION
2280              
2281             C<Net::API::CPAN> is a client to issue queries to the MetaCPAN REST API.
2282              
2283             Make sure to check out the L</"TERMINOLOGY"> section for the exact meaning of key words used in this documentation.
2284              
2285             =head1 CONSTRUCTOR
2286              
2287             =head2 new
2288              
2289             This instantiates a new L<Net::API::CPAN> object. This accepts the following options, which can later also be set using their associated method.
2290              
2291             =over 4
2292              
2293             =item * C<api_version>
2294              
2295             Integer. This is the C<CPAN> API version, and defaults to C<1>.
2296              
2297             =item * C<debug>
2298              
2299             Integer. This sets the debugging level. Defaults to 0. The higher and the more verbose will be the debugging output on STDERR.
2300              
2301             =item * C<ua>
2302              
2303             An optional L<HTTP::Promise> object. If not provided, one will be instantiated automatically.
2304              
2305             =back
2306              
2307             =head1 METHODS
2308              
2309             =head2 api_uri
2310              
2311             Sets or gets the C<CPAN> API C<URI> to use. This defaults to the C<Net::API::CPAN> constant C<API_URI> followed by the API version, such as:
2312              
2313             https://fastapi.metacpan.org/v1
2314              
2315             This returns an L<URI> object.
2316              
2317             =head2 api_version
2318              
2319             Sets or gets the C<CPAN> API version. As of 2023-09-01, this can only C<1>
2320              
2321             This returns a L<scalar object|Module::Generic::Scalar>
2322              
2323             =head2 cache_file
2324              
2325             Sets or gets a cache file path to use instead of issuing the C<HTTP> request. This affects how L</fetch> works since it does not issue an actual C<HTTP> request, but does not change the rest of the workflow.
2326              
2327             Returns a L<file object|Module::Generic::File> or C<undef> if nothing was set.
2328              
2329             =head2 fetch
2330              
2331             This takes an object type, such as C<author>, C<release>, C<file>, etc, and the following options and performs an C<HttP> request to the remote MetaCPAN REST API and return the appropriate data or object.
2332              
2333             If an error occurs, this set an L<error object|Net::API::CPAN::Error> and return C<undef> in scalar context, and an empty list in list context.
2334              
2335             =over 4
2336              
2337             =item * C<class>
2338              
2339             One of C<Net::API::CPAN> classes, such as L<Net::API::CPAN::Author>
2340              
2341             =item * C<endpoint>
2342              
2343             The endpoint to access, such as C</author>
2344              
2345             =item * C<headers>
2346              
2347             An array reference of headers with their corresponding values.
2348              
2349             =item * C<method>
2350              
2351             The C<HTTP> method to use. This defaults to C<GET>. This is case insensitive.
2352              
2353             =item * C<payload>
2354              
2355             The C<POST> payload to send to the remote MetaCPAN API. It must be already encoded in C<UTF-8>.
2356              
2357             =item * C<postprocess>
2358              
2359             A subroutine reference or an anonymous subroutine that will be call backed, taking the data received as the sole argument and returning the modified data.
2360              
2361             =item * C<query>
2362              
2363             An hash reference of key-value pairs representing the query string elements. This will be passed to L<URI/query_form>, so make sure to check what data structure is acceptable by L<URI>
2364              
2365             =item * C<request>
2366              
2367             An L<HTTP::Promise::Request> object.
2368              
2369             =back
2370              
2371             =head2 http_request
2372              
2373             The latest L<HTTP::Promise::Request> issued to the remote MetaCPAN API server.
2374              
2375             =head2 http_response
2376              
2377             The latest L<HTTP::Promise::Response> received from the remote MetaCPAN API server.
2378              
2379             =head2 json
2380              
2381             Returns a new L<JSON> object.
2382              
2383             =head2 new_filter
2384              
2385             This instantiates a new L<Net::API::CPAN::Filter>, passing whatever arguments were received, and setting the debugging mode too.
2386              
2387             =head1 API METHODS
2388              
2389             =head2 activity
2390              
2391             # Get all the release activity for author OALDERS in the last 24 months
2392             my $activity_obj = $cpan->activity(
2393             author => 'OALDERS',
2394             # distribution => 'HTTP-Message',
2395             # module => 'HTTP::Message',
2396             interval => '1M',
2397             ) || die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2398              
2399             # Get all the release activity that depend on HTTP::Message in the last 24 months
2400             my $activity_obj = $cpan->activity(
2401             # author => 'OALDERS',
2402             # distribution => 'HTTP-Message',
2403             module => 'HTTP::Message',
2404             interval => '1M',
2405             ) || die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2406              
2407             This method is used to query the CPAN REST API for the release activity for all, or for a given C<author>, or a given C<distribution>, or a given C<module> dependency. An optional aggregation interval can be stipulated with C<res> and it defaults to C<1w> (set by the API).
2408              
2409             =over 4
2410              
2411             =item * C<author> -> C</activity>
2412              
2413             If a string is provided representing a specific C<author>, this will issue a query to the API endpoint C</activity> to retrieve the release activity for that C<author> for the past 24 months for the specified author, such as:
2414              
2415             /activity?author=OALDERS
2416              
2417             For example:
2418              
2419             my $activity_obj = $cpan->activity(
2420             author => 'OALDERS',
2421             interval => '1M',
2422             ) || die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2423              
2424             This would return, upon success, a L<Net::API::CPAN::Activity> object containing release activity for the C<author> C<OALDERS> for the past 24 months.
2425              
2426             Note that the value of the C<author> is case insensitive and will automatically be transformed in upper case, so you could also do:
2427              
2428             Possible options are:
2429              
2430             =over 8
2431              
2432             =item * C<interval>
2433              
2434             Specifies an interval for the aggregate value. Defaults to C<1w>, which is 1 week. See L<ElasticSearch document|https://www.elastic.co/guide/en/elasticsearch/reference/2.4/query-dsl-range-query.html#_date_format_in_range_queries> for the proper value to use as interval.
2435              
2436             =item * C<new>
2437              
2438             Limit the result to newly issued distributions.
2439              
2440             =back
2441              
2442             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Factivity%3Fauthor%3DOALDERS> to see the data returned by the CPAN REST API.
2443              
2444             =item * C<distribution> -> C</activity>
2445              
2446             If a string is provided representing a specific C<distribution>, this will issue a query to the API endpoint C</activity> to retrieve the release activity for that C<distribution> for the past 24 months for the specified C<distribution>, such as:
2447              
2448             /activity?distribution=HTTP-Message
2449              
2450             For example:
2451              
2452             my $activity_obj = $cpan->activity(
2453             distribution => 'HTTP-Message',
2454             interval => '1M',
2455             ) || die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2456              
2457             This would return, upon success, a L<Net::API::CPAN::Activity> object containing release activity for the C<distribution> C<HTTP-Message> for the past 24 months.
2458              
2459             Possible options are:
2460              
2461             =over 8
2462              
2463             =item * C<interval>
2464              
2465             Specifies an interval for the aggregate value. Defaults to C<1w>, which is 1 week. See L<ElasticSearch document|https://www.elastic.co/guide/en/elasticsearch/reference/2.4/query-dsl-range-query.html#_date_format_in_range_queries> for the proper value to use as interval.
2466              
2467             =back
2468              
2469             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Factivity%3Fdistribution%3DHTTP-Message> to see the data returned by the CPAN REST API.
2470              
2471             =item * C<module> -> C</activity>
2472              
2473             If a string is provided representing a specific C<module>, this will issue a query to the API endpoint C</activity> to retrieve the release activity that have a dependency on that C<module> for the past 24 months, such as:
2474              
2475             /activity?res=1M&module=HTTP::Message
2476              
2477             For example:
2478              
2479             my $activity_obj = $cpan->activity(
2480             module => 'HTTP::Message',
2481             interval => '1M',
2482             ) || die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2483              
2484             This would return, upon success, a L<Net::API::CPAN::Activity> object containing release activity for all the distributions depending on the C<module> C<HTTP::Message> for the past 24 months.
2485              
2486             Possible options are:
2487              
2488             =over 8
2489              
2490             =item * C<interval>
2491              
2492             Specifies an interval for the aggregate value. Defaults to C<1w>, which is 1 week. See L<ElasticSearch document|https://www.elastic.co/guide/en/elasticsearch/reference/2.4/query-dsl-range-query.html#_date_format_in_range_queries> for the proper value to use as interval.
2493              
2494             =item * C<new>
2495              
2496             Limit the result to newly issued distributions.
2497              
2498             =back
2499              
2500             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Factivity%3Fres%3D1M%26module%3DHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
2501              
2502             =item * C<new> -> C</activity>
2503              
2504             If C<new> is provided with any value (true or not does not matter), this will issue a query to the API endpoint C</activity> to retrieve the new release activity in the past 24 months, such as:
2505              
2506             /activity?res=1M&new_dists=n
2507              
2508             For example:
2509              
2510             my $activity_obj = $cpan->activity(
2511             new => 1,
2512             interval => '1M',
2513             ) || die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2514              
2515             This would return, upon success, a L<Net::API::CPAN::Activity> object containing all new distributions release activity for the past 24 months.
2516              
2517             Possible options are:
2518              
2519             =over 8
2520              
2521             =item * C<interval>
2522              
2523             Specifies an interval for the aggregate value. Defaults to C<1w>, which is 1 week. See L<ElasticSearch document|https://www.elastic.co/guide/en/elasticsearch/reference/2.4/query-dsl-range-query.html#_date_format_in_range_queries> for the proper value to use as interval.
2524              
2525             =back
2526              
2527             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Factivity%3Fres%3D1M%26new_dists%3Dn> to see the data returned by the CPAN REST API.
2528              
2529             =back
2530              
2531             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
2532              
2533             =head2 author
2534              
2535             # Retrieves the information details for the specified author
2536             my $author_obj = $cpan->author( 'OALDERS' ) ||
2537             die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2538              
2539             # Retrieves author information details for the specified pause IDs
2540             my $list_obj = $cpan->author( [qw( OALDERS NEILB )] ) ||
2541             die( "Error with code: ", $cpan->error->code, " and message: ", $cpan->error->message );
2542              
2543             # Queries authors information details
2544             my $list_obj = $cpan->author(
2545             query => 'Olaf',
2546             from => 10,
2547             size => 20,
2548             ) || die( $cpan->error );
2549              
2550             # Queries authors information details using ElasticSearch format
2551             my $list_obj = $cpan->author( $filter_object ) ||
2552             die( $cpan->error );
2553              
2554             # Queries authors information using a prefix
2555             my $list_obj = $cpan->author( prefix => 'O' ) ||
2556             die( $cpan->error );
2557              
2558             # Retrieves authors information using their specified IDs
2559             my $list_obj = $cpan->author( user => [qw( FepgBJBZQ8u92eG_TcyIGQ 6ZuVfdMpQzy75_Mazx2_nw )] ) ||
2560             die( $cpan->error );
2561              
2562             This method is used to query the CPAN REST API for a specific C<author>, a list of C<authors>, or search an C<author> using a query.
2563             It takes a string, an array reference, an hash or alternatively an hash reference as possible parameters.
2564              
2565             =over 4
2566              
2567             =item * C<author> -> C</author/{author}>
2568              
2569             If a string is provided representing a specific C<author>, this will issue a query to the API endpoint C</author/{author}> to retrieve the information details for the specified author, such as:
2570              
2571             /author/OALDERS
2572              
2573             For example:
2574              
2575             my $author_obj = $cpan->author( 'OALDERS' ) ||
2576             die( $cpan->error );
2577              
2578             This would return, upon success, a L<Net::API::CPAN::Author> object.
2579              
2580             Note that the value of the C<author> is case insensitive and will automatically be transformed in upper case, so you could also do:
2581              
2582             my $author_obj = $cpan->author( 'OAlders' ) ||
2583             die( $cpan->error );
2584              
2585             The following options are also supported:
2586              
2587             =over 8
2588              
2589             =item * C<from>
2590              
2591             An integer representing the offset starting from 0 within the total data.
2592              
2593             =item * C<size>
2594              
2595             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
2596              
2597             =back
2598              
2599             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fauthor%2FOALDERS> to see the data returned by the CPAN REST API.
2600              
2601             =item * [C<author>] -> C</author/by_ids>
2602              
2603             And providing an array reference of C<authors> will trigger a query to the API endpoint C</author/by_ids>, such as:
2604              
2605             /author/by_ids?id=OALDERS&id=NEILB
2606              
2607             For example:
2608              
2609             my $list_obj = $cpan->author( [qw( OALDERS NEILB )] ) ||
2610             die( $cpan->error );
2611              
2612             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Author> objects.
2613              
2614             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fauthor%2Fby_ids%3Fid%3DOALDERS%26id%3DNEILB> to see the data returned by the CPAN REST API.
2615              
2616             =item * C<query> -> C</author>
2617              
2618             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</author>, such as:
2619              
2620             /author?q=Tokyo
2621              
2622             For example:
2623              
2624             my $list_obj = $cpan->author(
2625             query => 'Tokyo',
2626             from => 10,
2627             size => 10,
2628             ) || die( $cpan->error );
2629              
2630             will find all C<authors> related to Tokyo.
2631              
2632             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Author> objects.
2633              
2634             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fauthor%3Fq%3DTokyo> to see the data returned by the CPAN REST API.
2635              
2636             =item * C<prefix> -> C</author/by_prefix/{prefix}>
2637              
2638             However, if the property C<prefix> is provided, this will issue a query to the endpoint C</author/by_prefix/{prefix}>, such as:
2639              
2640             /author/by_prefix/O
2641              
2642             which will find all C<authors> whose Pause ID starts with the specified prefix; in this example, the letter C<O>
2643              
2644             For example:
2645              
2646             my $list_obj = $cpan->author( prefix => 'O' ) ||
2647             die( $cpan->error );
2648              
2649             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Author> objects.
2650              
2651             The following options are also supported:
2652              
2653             =over 8
2654              
2655             =item * C<from>
2656              
2657             An integer representing the offset starting from 0 within the total data.
2658              
2659             =item * C<size>
2660              
2661             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
2662              
2663             =back
2664              
2665             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fauthor%2Fby_prefix%2FO> to see the data returned by the CPAN REST API.
2666              
2667             =item * C<user> -> C</author/by_user>
2668              
2669             And if the property C<user> is provided, this will issue a query to the endpoint C</author/by_user>, such as:
2670              
2671             /author/by_user?user=FepgBJBZQ8u92eG_TcyIGQ&user=6ZuVfdMpQzy75_Mazx2_nw
2672              
2673             which will fetch the information for the authors whose user ID are C<FepgBJBZQ8u92eG_TcyIGQ> and C<6ZuVfdMpQzy75_Mazx2_nw> (here respectively corresponding to the C<authors> C<OALDERS> and C<HAARG>)
2674              
2675             For example:
2676              
2677             my $list_obj = $cpan->author( user => [qw( FepgBJBZQ8u92eG_TcyIGQ 6ZuVfdMpQzy75_Mazx2_nw )] ) ||
2678             die( $cpan->error );
2679              
2680             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Author> objects.
2681              
2682             However, note that not all C<CPAN> account have a user ID, surprisingly enough.
2683              
2684             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fauthor%2Fby_user%3Fuser%3DFepgBJBZQ8u92eG_TcyIGQ%26user%3D6ZuVfdMpQzy75_Mazx2_nw> to see the data returned by the CPAN REST API.
2685              
2686             =item * L<search filter|Net::API::CPAN::Filter> -> C</author/_search>
2687              
2688             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</author/_search> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
2689              
2690             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Author> objects.
2691              
2692             =back
2693              
2694             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
2695              
2696             =head2 autocomplete
2697              
2698             This takes a string and will issue a query to the endpoint C</search/autocomplete> to retrieve the result set based on the autocomplete search query specified, such as:
2699              
2700             /search/autocomplete?q=HTTP
2701              
2702             For example:
2703              
2704             my $list_obj = $cpan->autocomplete( 'HTTP' ) || die( $cpan->error );
2705              
2706             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::File> objects.
2707              
2708             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fautocomplete%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
2709              
2710             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
2711              
2712             =head2 changes
2713              
2714             # Retrieves the specified distribution Changes file content
2715             my $change_obj = $cpan->changes( distribution => 'HTTP-Message' ) ||
2716             die( $cpan->error );
2717              
2718             # Retrieves one or more distribution Changes file details using author and release information
2719             my $change_obj = $cpan->changes(
2720             author => 'OALDERS',
2721             release => 'HTTP-Message-6.36'
2722             ) || die( $cpan->error );
2723              
2724             # Same:
2725             my $change_obj = $cpan->changes( release => 'OALDERS/HTTP-Message-6.36' ) ||
2726             die( $cpan->error );
2727              
2728             # With multiple author and releases
2729             my $list_obj = $cpan->changes(
2730             author => [qw( OALDERS NEILB )],
2731             release => [qw( HTTP-Message-6.36 Data-HexDump-0.04 )]
2732             ) || die( $cpan->error );
2733              
2734             # Same:
2735             my $list_obj = $cpan->changes( release => [qw( OALDERS/HTTP-Message-6.36 NEILB/Data-HexDump-0.04 )] ) ||
2736             die( $cpan->error );
2737              
2738             This method is used to query the CPAN REST API for one or more particular C<release>'s C<Changes> (or C<CHANGES> depending on the release) file content.
2739              
2740             =over 4
2741              
2742             =item * C<distribution> -> C</changes/{distribution}>
2743              
2744             If the property C<distribution> is provided, this will issue a query to the endpoint C</changes/{distribution}> to retrieve a distribution Changes file details, such as:
2745              
2746             /changes/HTTP-Message
2747              
2748             For example:
2749              
2750             my $change_obj = $cpan->changes( distribution => 'HTTP-Message' ) ||
2751             die( $cpan->error );
2752              
2753             which will retrieve the C<Changes> file information for the B<latest> C<release> of the specified C<distribution>, and return a L<Net::API::CPAN::Changes> object upon success.
2754              
2755             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fchanges%2FHTTP-Message> to see the data returned by the CPAN REST API.
2756              
2757             =item * C<release> -> C</changes/>
2758              
2759             =item * C<author> and C<release> -> C</changes/{author}/{release}>
2760              
2761             If the properties C<author> and C<release> have been provided or that the value of the property C<release> has the form C<author>/C<release>, this will issue a query to the endpoint C</changes/{author}/{release}> to retrieve an author distribution Changes file details:
2762              
2763             /changes/OALDERS/HTTP-Message-6.36
2764              
2765             For example:
2766              
2767             my $change_obj = $cpan->changes(
2768             author => 'OALDERS',
2769             release => 'HTTP-Message-6.36'
2770             ) || die( $cpan->error );
2771             # or
2772             my $change_obj = $cpan->changes( release => 'OALDERS/HTTP-Message-6.36' ) ||
2773             die( $cpan->error );
2774              
2775             which will retrieve the C<Changes> file information for the specified C<release>, and return, upon success, a L<Net::API::CPAN::Changes> object.
2776              
2777             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fchanges%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
2778              
2779             =item * [C<author>] and [C<release>] -> C</author/by_releases>
2780              
2781             And, if both properties C<author> and C<release> have been provided and are both an array reference of equal size, this will issue a query to the endpoint C</author/by_releases> to retrieve one or more distribution Changes file details using the specified author and release information, such as:
2782              
2783             /changes/by_releases?release=OALDERS%2FHTTP-Message-6.37&release=NEILB%2FData-HexDump-0.04
2784              
2785             For example:
2786              
2787             my $list_obj = $cpan->changes(
2788             author => [qw( OALDERS NEILB )],
2789             release => [qw( HTTP-Message-6.36 Data-HexDump-0.04 )]
2790             ) || die( $cpan->error );
2791              
2792             Alternatively, you can provide the property C<release> having, as value, an array reference of C<author>/C<release>, such as:
2793              
2794             my $list_obj = $cpan->changes(
2795             release => [qw(
2796             OALDERS/HTTP-Message-6.36
2797             NEILB/Data-HexDump-0.04
2798             )]
2799             ) || die( $cpan->error );
2800              
2801             which will retrieve the C<Changes> file information for the specified C<releases>, and return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Changes> objects.
2802              
2803             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fchanges%2Fby_releases%3Frelease%3DOALDERS%252FHTTP-Message-6.37%26release%3DNEILB%252FData-HexDump-0.04> to see the data returned by the CPAN REST API.
2804              
2805             =back
2806              
2807             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
2808              
2809             =head2 clientinfo
2810              
2811             This issue a query to the endpoint C<https://clientinfo.metacpan.org> and retrieves the information of the various base URL.
2812              
2813             It returns an hash reference with the following structure:
2814              
2815             {
2816             future => {
2817             domain => "https://fastapi.metacpan.org/",
2818             url => "https://fastapi.metacpan.org/v1/",
2819             version => "v1",
2820             },
2821             production => {
2822             domain => "https://fastapi.metacpan.org/",
2823             url => "https://fastapi.metacpan.org/v1/",
2824             version => "v1",
2825             },
2826             testing => {
2827             domain => "https://fastapi.metacpan.org/",
2828             url => "https://fastapi.metacpan.org/v1/",
2829             version => "v1",
2830             },
2831             }
2832              
2833             Each of the URL is an L<URL> object.
2834              
2835             =head2 contributor
2836              
2837             # Retrieves a list of module contributed to by the specified PauseID
2838             my $list_obj = $cpan->contributor( author => 'OALDERS' ) ||
2839             die( $cpan->error );
2840              
2841             # Retrieves a list of module contributors details
2842             my $list_obj = $cpan->contributor(
2843             author => 'OALDERS'
2844             release => 'HTTP-Message-6.37'
2845             ) || die( $cpan->error );
2846              
2847             This method is used to query the CPAN REST API for either the list of C<releases> a CPAN account has contributed to, or to get the list of C<contributors> for a specified C<release>.
2848              
2849             =over 4
2850              
2851             =item * C<author> -> C</contributor/by_pauseid/{author}>
2852              
2853             If the property C<author> is provided, this will issue a query to the endpoint C</contributor/by_pauseid/{author}> to retrieve a list of module contributed to by the specified PauseID, such as:
2854              
2855             /contributor/by_pauseid/OALDERS
2856              
2857             For example:
2858              
2859             my $list_obj = $cpan->contributor( author => 'OALDERS' ) ||
2860             die( $cpan->error );
2861              
2862             This will, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Contributor> objects containing the details of the release to which the specified C<author> has contributed.
2863              
2864             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fcontributor%2Fby_pauseid%2FOALDERS> to see the data returned by the CPAN REST API.
2865              
2866             =item * C<author> and C<release> -> C</contributor/{author}/{release}>
2867              
2868             And if the properties C<author> and C<release> are provided, this will issue a query to the endpoint C</contributor/{author}/{release}> to retrieve a list of release contributors details, such as:
2869              
2870             /contributor/OALDERS/HTTP-Message-6.36
2871              
2872             For example:
2873              
2874             my $list_obj = $cpan->contributor(
2875             author => 'OALDERS'
2876             release => 'HTTP-Message-6.37'
2877             ) || die( $cpan->error );
2878              
2879             This will, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Contributor> objects containing the specified C<release> information and the C<pauseid> of all the C<authors> who have contributed to the specified C<release>.
2880              
2881             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fcontributor%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
2882              
2883             =back
2884              
2885             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
2886              
2887             =head2 cover
2888              
2889             This method is used to query the CPAN REST API to the endpoint C</v1/cover/{release}> to get the C<cover> information including C<distribution> name, C<release> name, C<version> and download C<URL>, such as:
2890              
2891             /cover/HTTP-Message-6.37
2892              
2893             For example:
2894              
2895             my $cover_obj = $cpan->cover(
2896             release => 'HTTP-Message-6.37',
2897             ) || die( $cpan->error );
2898              
2899             It returns, upon success, a L<Net::API::CPAN::Cover> object.
2900              
2901             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
2902              
2903             =head2 diff
2904              
2905             # Retrieves a diff of two files with output as JSON
2906             my $diff_obj = $cpan->diff(
2907             file1 => 'AcREzFgg3ExIrFTURa0QJfn8nto',
2908             file2 => 'Ies7Ysw0GjCxUU6Wj_WzI9s8ysU',
2909             # Default
2910             accept => 'application/json',
2911             ) || die( $cpan->error );
2912              
2913             # Retrieves a diff of two files with output as plain text
2914             my $diff_text = $cpan->diff(
2915             file1 => 'AcREzFgg3ExIrFTURa0QJfn8nto',
2916             file2 => 'Ies7Ysw0GjCxUU6Wj_WzI9s8ysU',
2917             # Default
2918             accept => 'text/plain',
2919             ) || die( $cpan->error );
2920              
2921             # Retrieves a diff of two releases with output as JSON
2922             my $diff_obj = $cpan->diff(
2923             author1 => 'OALDERS',
2924             # This is optional if it is the same
2925             author2 => 'OALDERS',
2926             release1 => 'HTTP-Message-6.35'
2927             release2 => 'HTTP-Message-6.36'
2928             # Default
2929             accept => 'application/json',
2930             ) || die( $cpan->error );
2931              
2932             # Retrieves a diff of two releases with output as plain text
2933             my $diff_text = $cpan->diff(
2934             author1 => 'OALDERS',
2935             # This is optional if it is the same
2936             author2 => 'OALDERS',
2937             release1 => 'HTTP-Message-6.35'
2938             release2 => 'HTTP-Message-6.36'
2939             # Default
2940             accept => 'text/plain',
2941             ) || die( $cpan->error );
2942              
2943             # Retrieves a diff of the latest release and its previous version with output as JSON
2944             my $diff_obj = $cpan->diff(
2945             distribution => 'HTTP-Message',
2946             # Default
2947             accept => 'application/json',
2948             ) || die( $cpan->error );
2949              
2950             # Retrieves a diff of the latest release and its previous version with output as plain text
2951             my $diff_text = $cpan->diff(
2952             distribution => 'HTTP-Message',
2953             # Default
2954             accept => 'text/plain',
2955             ) || die( $cpan->error );
2956              
2957             This method is used to query the CPAN REST API to get the C<diff> output between 2 files, or 2 releases.
2958              
2959             =over 4
2960              
2961             =item * C<file1> and C<file2> -> C</diff/file/{file1}/{file2}>
2962              
2963             If the properties C<file1> and C<file2> are provided, this will issue a query to the endpoint C</diff/file/{file1}/{file2}>, such as:
2964              
2965             /diff/file/AcREzFgg3ExIrFTURa0QJfn8nto/Ies7Ysw0GjCxUU6Wj_WzI9s8ysU
2966              
2967             The result returned will depend on the optional C<accept> property, which is, by default C<application/json>, but can also be set to C<text/plain>.
2968              
2969             When set to C<application/json>, this will retrieve the result as C<JSON> data and return a L<Net::API::CPAN::Diff> object. If this is set to C<text/plain>, then this will return a raw C<diff> output as a string encoded in L<Perl internal utf-8 encoding|perlunicode>.
2970              
2971             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fdiff%2Ffile%2FAcREzFgg3ExIrFTURa0QJfn8nto%2FIes7Ysw0GjCxUU6Wj_WzI9s8ysU> to see the data returned by the CPAN REST API.
2972              
2973             =item * C<author1>, C<author2>, C<release1>, and C<release2> -> C</diff/release/{author1}/{release1}/{author2}/{release2}>
2974              
2975             If the properties C<author1>, C<author2>, C<release1>, and C<release2> are provided, this will issue a query to the endpoint C</diff/release/{author1}/{release1}/{author2}/{release2}>, such as:
2976              
2977             /diff/release/OALDERS/HTTP-Message-6.35/OALDERS/HTTP-Message-6.36
2978              
2979             For example:
2980              
2981             my $diff_obj = $cpan->diff(
2982             author1 => 'OALDERS',
2983             # This is optional if it is the same
2984             author2 => 'OALDERS',
2985             release1 => 'HTTP-Message-6.35'
2986             release2 => 'HTTP-Message-6.36'
2987             # Default
2988             accept => 'application/json',
2989             ) || die( $cpan->error );
2990              
2991             Note that, if C<author1> and C<author2> are the same, C<author2> is optional.
2992              
2993             It is important, however, that the C<release> specified with C<release1> belongs to C<author1> and the C<release> specified with C<release2> belongs to C<author2>
2994              
2995             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fdiff%2Frelease%2FOALDERS%2FHTTP-Message-6.35%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
2996              
2997             =item * C<distribution> -> C</diff/release/{distribution}>
2998              
2999             You can also specify the property C<distribution>, and this will issue a query to the endpoint C</diff/release/{distribution}>, such as:
3000              
3001             /diff/release/HTTP-Message
3002              
3003             For example:
3004              
3005             my $diff_obj = $cpan->diff(
3006             distribution => 'HTTP-Message',
3007             # Default
3008             accept => 'application/json',
3009             ) || die( $cpan->error );
3010              
3011             If C<accept> is set to C<application/json>, which is the default value, this will return a L<Net::API::CPAN::Diff> object representing the difference between the previous version and current version for the C<release> of the C<distribution> specified. If, however, C<accept> is set to C<text/plain>, a string of the diff output will be returned encoded in L<Perl internal utf-8 encoding|perlunicode>.
3012              
3013             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fdiff%2Frelease%2FHTTP-Message> to see the data returned by the CPAN REST API.
3014              
3015             =back
3016              
3017             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3018              
3019             =head2 distribution
3020              
3021             # Retrieves a distribution information details
3022             my $dist_obj = $cpan->distribution( 'HTTP-Message' ) ||
3023             die( $cpan->error );
3024              
3025             # Queries distribution information details using simple search
3026             my $list_obj = $cpan->distribution(
3027             query => 'HTTP',
3028             from => 10,
3029             size => 10,
3030             ) || die( $cpan->error );
3031            
3032             # Queries distribution information details using advanced search with ElasticSearch
3033             my $list_obj = $cpan->distribution( $filter_object ) ||
3034             die( $cpan->error );
3035              
3036             This method is used to query the CPAN REST API to retrieve C<distribution> information.
3037              
3038             =over 4
3039              
3040             =item * C<distribution> -> C</distribution/{distribution}>
3041              
3042             If a string representing a C<distribution> is provided, it will issue a query to the endpoint C</distribution/{distribution}> to retrieve a distribution information details, such as:
3043              
3044             /distribution/HTTP-Message
3045              
3046             For example:
3047              
3048             my $dist_obj = $cpan->distribution( 'HTTP-Message' ) ||
3049             die( $cpan->error );
3050              
3051             This will return, upon success, a L<Net::API::CPAN::Distribution> object.
3052              
3053             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fdistribution%2FHTTP-Message> to see the data returned by the CPAN REST API.
3054              
3055             =item * C<query> -> C</distribution>
3056              
3057             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</distribution>, such as:
3058              
3059             /distribution?q=HTTP
3060              
3061             For example:
3062              
3063             my $list_obj = $cpan->distribution(
3064             query => 'HTTP',
3065             from => 10,
3066             size => 10,
3067             ) || die( $cpan->error );
3068              
3069             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Distribution> objects.
3070              
3071             The following options are also supported:
3072              
3073             =over 8
3074              
3075             =item * C<from>
3076              
3077             An integer representing the offset starting from 0 within the total data.
3078              
3079             =item * C<size>
3080              
3081             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3082              
3083             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fdistribution%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3084              
3085             =item * L<search filter|Net::API::CPAN::Filter> -> C</distribution>
3086              
3087             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</distribution> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
3088              
3089             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Distribution> objects.
3090              
3091             =back
3092              
3093             =back
3094              
3095             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3096              
3097             =head2 download_url
3098              
3099             # Retrieve the latest release download URL information details
3100             my $dl_obj = $cpan->download_url( 'HTTP::Message' ) ||
3101             die( $cpan->error );
3102              
3103             This method is used to query the CPAN REST API to retrieve the specified C<module> latest C<release> C<download_url> information.
3104              
3105             =over 4
3106              
3107             =item * C<module> -> C</download_url/{module}>
3108              
3109             If a string representing a C<module> is provided, it will issue a query to the endpoint C</download_url/{module}> to retrieve the download URL information details of the specified module, such as:
3110              
3111             /download_url/HTTP::Message
3112              
3113             This will return, upon success, a L<Net::API::CPAN::DownloadUrl> object.
3114              
3115             The following options are also supported:
3116              
3117             =over 8
3118              
3119             =item * C<dev>
3120              
3121             # Retrieves a development release
3122             my $dl_obj = $cpan->download_url( 'HTTP::Message',
3123             {
3124             dev => 1,
3125             version => '>1.01',
3126             }) || die( $cpan->error );
3127              
3128             Specifies if the C<release> is a development version.
3129              
3130             =item * C<version>
3131              
3132             # Retrieve the download URL of a specific release version
3133             my $dl_obj = $cpan->download_url( 'HTTP::Message',
3134             {
3135             version => '1.01',
3136             }) || die( $cpan->error );
3137              
3138             # or, using a range
3139             my $dl_obj = $cpan->download_url( 'HTTP::Message',
3140             {
3141             version => '<=1.01',
3142             }) || die( $cpan->error );
3143             my $dl_obj = $cpan->download_url( 'HTTP::Message',
3144             {
3145             version => '>1.01,<=2.00',
3146             }) || die( $cpan->error );
3147              
3148             Specifies the version requirement or version range requirement.
3149              
3150             Supported range operators are C<==> C<!=> C<< <= >> C<< >= >> C<< < >> C<< > >> C<!>
3151              
3152             Separate the ranges with a comma when specifying multiple ranges.
3153              
3154             =back
3155              
3156             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fdownload_url%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
3157              
3158             =back
3159              
3160             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3161              
3162             =head2 favorite
3163              
3164             # Queries favorites using a simple search
3165             my $list_obj = $cpan->favorite(
3166             query => 'HTTP',
3167             from => 10,
3168             size => 10,
3169             ) || die( $cpan->error );
3170              
3171             # Queries favorites using a advanced search with ElasticSearch format
3172             my $list_obj = $cpan->favorite( $filter_object ) ||
3173             die( $cpan->error );
3174              
3175             # Retrieves favorites agregate by distributions as an hash reference
3176             # e.g.: HTTP-Message => 63
3177             my $hash_ref = $cpan->favorite( aggregate => 'HTTP-Message' ) ||
3178             die( $cpan->error );
3179              
3180             # Same
3181             my $hash_ref = $cpan->favorite( agg => 'HTTP-Message' ) ||
3182             die( $cpan->error );
3183              
3184             # Same with multiple distributions
3185             my $hash_ref = $cpan->favorite( aggregate => [qw( HTTP-Message Data-HexDump)] ) ||
3186             die( $cpan->error );
3187              
3188             # Same
3189             my $hash_ref = $cpan->favorite( agg => [qw( HTTP-Message Data-HexDump)] ) ||
3190             die( $cpan->error );
3191              
3192             # Retrieves list of users who favorited a distribution as an array reference
3193             # e.g. [ '9nGbVdZ4QhO4Ia5ZhNpjtg', 'c4QLX0YORN6-quL15MGwqg', ... ]
3194             my $array_ref = $cpan->favorite( distribution => 'HTTP-Message' ) ||
3195             die( $cpan->error );
3196              
3197             # Retrieves user favorites information details
3198             my $list_obj = $cpan->favorite( user => 'q_15sjOkRminDY93g9DuZQ' ) ||
3199             die( $cpan->error );
3200              
3201             # Retrieves top favorite distributions a.k.a. leaderboard as an array reference
3202             my $array_ref = $cpan->favorite( leaderboard => 1 ) ||
3203             die( $cpan->error );
3204              
3205             # Retrieves list of recent favorite distribution
3206             my $list_obj = $cpan->favorite( recent => 1 ) ||
3207             die( $cpan->error );
3208              
3209             This method is used to query the CPAN REST API to retrieve C<favorite> information.
3210              
3211             =over 4
3212              
3213             =item * C<query> -> C</favorite>
3214              
3215             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</favorite>, such as:
3216              
3217             /favorite?q=HTTP
3218              
3219             For example:
3220              
3221             my $list_obj = $cpan->favorite( query => 'HTTP' ) ||
3222             die( $cpan->error );
3223              
3224             which will find all C<favorite> related to the query term C<HTTP>.
3225              
3226             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3227              
3228             The following options are also supported:
3229              
3230             =over 8
3231              
3232             =item * C<from>
3233              
3234             An integer representing the offset starting from 0 within the total data.
3235              
3236             =item * C<size>
3237              
3238             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3239              
3240             =back
3241              
3242             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffavorite%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3243              
3244             =item * L<search filter|Net::API::CPAN::Filter> -> C</favorite>
3245              
3246             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</favorite> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
3247              
3248             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3249              
3250             =item * C<aggregate> or C<agg> -> C</favorite/agg_by_distributions>
3251              
3252             If the property C<aggregate> or C<agg>, for short, is provided, this will issue a query to the endpoint C</favorite/agg_by_distributions> to retrieve favorites agregate by distributions, such as:
3253              
3254             /favorite/agg_by_distributions?distribution=HTTP-Message&distribution=Data-HexDump
3255              
3256             For example:
3257              
3258             my $hash_ref = $cpan->favorite( aggregate => 'HTTP-Message' ) ||
3259             die( $cpan->error );
3260             my $hash_ref = $cpan->favorite( aggregate => [qw( HTTP-Message Data-HexDump)] ) ||
3261             die( $cpan->error );
3262              
3263             The C<aggregate> value can be either a string representing a C<distribution>, or an array reference of C<distributions>
3264              
3265             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3266              
3267             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffavorite%2Fagg_by_distributions%3Fdistribution%3DHTTP-Message%26distribution%3DData-HexDump> to see the data returned by the CPAN REST API.
3268              
3269             =item * C<distribution> -> C</favorite/users_by_distribution/{distribution}>
3270              
3271             If the property C<distribution> is provided, will issue a query to the endpoint C</favorite/users_by_distribution/{distribution}> to retrieves the list of users who favorited the specified distribution, such as:
3272              
3273             /favorite/users_by_distribution/HTTP-Message
3274              
3275             For example:
3276              
3277             my $array_ref = $cpan->favorite( distribution => 'HTTP-Message' ) ||
3278             die( $cpan->error );
3279              
3280             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3281              
3282             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffavorite%2Fusers_by_distribution%2FHTTP-Message> to see the data returned by the CPAN REST API.
3283              
3284             =item * C<user> -> C</favorite/by_user/{user}>
3285              
3286             If the property C<user> is provided, this will issue a query to the endpoint C</favorite/by_user/{user}> to retrieve the specified user favorites information details, such as:
3287              
3288             /favorite/by_user/q_15sjOkRminDY93g9DuZQ
3289              
3290             For example:
3291              
3292             my $list_obj = $cpan->favorite( user => 'q_15sjOkRminDY93g9DuZQ' ) ||
3293             die( $cpan->error );
3294              
3295             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3296              
3297             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffavorite%2Fby_user%2Fq_15sjOkRminDY93g9DuZQ> to see the data returned by the CPAN REST API.
3298              
3299             =item * C<leaderboard> -> C</favorite/leaderboard>
3300              
3301             If the property C<leaderboard> is provided with any value true or false does not matter, this will issue a query to the endpoint C</favorite/leaderboard> to retrieve the top favorite distributions a.k.a. leaderboard, such as:
3302              
3303             /favorite/leaderboard
3304              
3305             For example:
3306              
3307             my $array_ref = $cpan->favorite( leaderboard => 1 ) ||
3308             die( $cpan->error );
3309              
3310             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3311              
3312             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffavorite%2Fleaderboard> to see the data returned by the CPAN REST API.
3313              
3314             =item * C<recent> -> C</favorite/recent>
3315              
3316             Finally, if the property C<recent> is provided with any value true or false does not matter, this will issue a query to the endpoint C</favorite/recent> to retrieve the list of recent favorite distributions, such as:
3317              
3318             /favorite/recent
3319              
3320             For example:
3321              
3322             my $list_obj = $cpan->favorite( recent => 1 ) ||
3323             die( $cpan->error );
3324              
3325             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Favorite> objects.
3326              
3327             The following options are also supported:
3328              
3329             =over 8
3330              
3331             =item * C<page>
3332              
3333             An integer representing the page offset starting from 1.
3334              
3335             =item * C<size>
3336              
3337             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3338              
3339             =back
3340              
3341              
3342             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffavorite%2Frecent> to see the data returned by the CPAN REST API.
3343              
3344             =back
3345              
3346             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3347              
3348             =head2 file
3349              
3350             # Queries files using simple search
3351             my $list_obj = $cpan->file(
3352             query => 'HTTP',
3353             from => 10,
3354             size => 10,
3355             ) || die( $cpan->error );
3356              
3357             # Queries files with advanced search using ElasticSearch
3358             my $list_obj = $cpan->file( $filter_object ) ||
3359             die( $cpan->error );
3360              
3361             # Retrieves a directory content
3362             my $list_obj = $cpan->file(
3363             author => 'OALDERS',
3364             release => 'HTTP-Message-6.36',
3365             dir => 'lib/HTTP',
3366             ) || die( $cpan->error );
3367              
3368             # Retrieves a file information details
3369             my $file_obj = $cpan->file(
3370             author => 'OALDERS',
3371             release => 'HTTP-Message-6.36',
3372             path => 'lib/HTTP/Message.pm',
3373             ) || die( $cpan->error );
3374              
3375             This method is used to query the CPAN REST API to retrieve C<file> information.
3376              
3377             =over 4
3378              
3379             =item * C<query> -> C</file>
3380              
3381             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</file>, such as:
3382              
3383             /file?q=HTTP
3384              
3385             For example:
3386              
3387             my $list_obj = $cpan->file(
3388             query => 'HTTP',
3389             from => 10,
3390             size => 10,
3391             ) || die( $cpan->error );
3392              
3393             will find all C<files> related to C<HTTP>.
3394              
3395             This would return a L<Net::API::CPAN::List> object upon success.
3396              
3397             The following options are also supported:
3398              
3399             =over 8
3400              
3401             =item * C<from>
3402              
3403             An integer representing the offset starting from 0 within the total data.
3404              
3405             =item * C<size>
3406              
3407             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3408              
3409             =back
3410              
3411             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffile%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3412              
3413             =item * L<search filter|Net::API::CPAN::Filter> -> C</file>
3414              
3415             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</file> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
3416              
3417             my $list_obj = $cpan->file( $filter_object ) ||
3418             die( $cpan->error );
3419              
3420             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::File> objects.
3421              
3422             =item * C<author>, C<release> and C<dir> -> C</file/dir/{author}/{release}/{dir}>
3423              
3424             If the properties, C<author>, C<release> and C<dir> are provided, this will issue a query to the endpoint C</file/dir/{author}/{release}/{dir}> to retrieve the specified directory content, such as:
3425              
3426             /file/dir/OALDERS/HTTP-Message-6.36/lib/HTTP
3427              
3428             For example:
3429              
3430             my $list_obj = $cpan->file(
3431             author => 'OALDERS',
3432             release => 'HTTP-Message-6.36',
3433             dir => 'lib/HTTP',
3434             ) || die( $cpan->error );
3435              
3436             For this to yield correct results, the C<dir> specified must be a directory.
3437              
3438             This would return, upon success, a L<Net::API::CPAN::List> object of all the files and directories contained within the specified directory.
3439              
3440             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffile%2Fdir%2FOALDERS%2FHTTP-Message-6.36%2Flib%2FHTTP> to see the data returned by the CPAN REST API.
3441              
3442             =item * C<author>, C<release> and C<path> -> C</file/{author}/{release}/{path}>
3443              
3444             If the properties, C<author>, C<release> and C<path> are provided, this will issue a query to the endpoint C</file/{author}/{release}/{path}> to retrieve the specified file (or directory) information details, such as:
3445              
3446             /file/OALDERS/HTTP-Message-6.36/lib/HTTP/Message.pm
3447              
3448             For example:
3449              
3450             my $file_obj = $cpan->file(
3451             author => 'OALDERS',
3452             release => 'HTTP-Message-6.36',
3453             path => 'lib/HTTP/Message.pm',
3454             ) || die( $cpan->error );
3455              
3456             This would return, upon success, a L<Net::API::CPAN::File> object of the information retrieved.
3457              
3458             Note that the path can point to either a file or a directory within the given release.
3459              
3460             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Ffile%2FOALDERS%2FHTTP-Message-6.36%2Flib%2FHTTP%2FMessage.pm> to see the data returned by the CPAN REST API.
3461              
3462             =back
3463              
3464             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3465              
3466             =head2 first
3467              
3468             This takes a string and will issue a query to the endpoint C</search/first> to retrieve the first result found based on the search query specified, such as:
3469              
3470             /search/first?q=HTTP
3471              
3472             For example:
3473              
3474             my $list_obj = $cpan->first( 'HTTP' ) || die( $cpan->error );
3475              
3476             This would, upon success, return a L<Net::API::CPAN::Module> object.
3477              
3478             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fautocomplete%2Fsuggest%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3479              
3480             =head2 history
3481              
3482             # Retrieves the history of a given module
3483             my $list_obj = $cpan->history(
3484             type => 'module',
3485             module => 'HTTP::Message',
3486             path => 'lib/HTTP/Message.pm',
3487             ) || die( $cpan->error );
3488              
3489             # Retrieves the history of a given distribution file
3490             my $list_obj = $cpan->history(
3491             type => 'file',
3492             distribution => 'HTTP-Message',
3493             path => 'lib/HTTP/Message.pm',
3494             ) || die( $cpan->error );
3495              
3496             # Retrieves the history of a given module documentation
3497             my $list_obj = $cpan->history(
3498             type => 'documentation',
3499             module => 'HTTP::Message',
3500             path => 'lib/HTTP/Message.pm',
3501             ) || die( $cpan->error );
3502              
3503             This method is used to query the CPAN REST API to retrieve C<file> history information.
3504              
3505             =over 4
3506              
3507             =item * C<module> -> C</search/history/module>
3508              
3509             If the property C<module> is provided, this will trigger a query to the endpoint C</search/history/module> to retrieve the history of a given module, such as:
3510              
3511             /search/history/module/HTTP::Message/lib/HTTP/Message.pm
3512              
3513             For example:
3514              
3515             my $list_obj = $cpan->history(
3516             type => 'module',
3517             module => 'HTTP::Message',
3518             path => 'lib/HTTP/Message.pm',
3519             ) || die( $cpan->error );
3520              
3521             will find all C<module> history related to the module C<HTTP::Message>.
3522              
3523             This would return a L<Net::API::CPAN::List> object upon success.
3524              
3525             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fhistory%2Fmodule%2FHTTP%3A%3AMessage%2Flib%2FHTTP%2FMessage.pm> to see the data returned by the CPAN REST API.
3526              
3527             =item * C<file> -> C</search/history/file>
3528              
3529             If the property C<file> is provided, this will trigger a query to the endpoint C</search/history/file> to retrieve the history of a given distribution file, such as:
3530              
3531             /search/history/file/HTTP-Message/lib/HTTP/Message.pm
3532              
3533             For example:
3534              
3535             my $list_obj = $cpan->history(
3536             type => 'file',
3537             distribution => 'HTTP-Message',
3538             path => 'lib/HTTP/Message.pm',
3539             ) || die( $cpan->error );
3540              
3541             will find all C<files> history related to the distribution C<HTTP-Message>.
3542              
3543             This would return a L<Net::API::CPAN::List> object upon success.
3544              
3545             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fhistory%2Ffile%2FHTTP-Message%2Flib%2FHTTP%2FMessage.pm> to see the data returned by the CPAN REST API.
3546              
3547             =item * C<documentation> -> C</search/history/documentation>
3548              
3549             If the property C<documentation> is provided, this will trigger a query to the endpoint C</search/history/documentation> to retrieve the history of a given module documentation, such as:
3550              
3551             /search/history/documentation/HTTP::Message/lib/HTTP/Message.pm
3552              
3553             For example:
3554              
3555             my $list_obj = $cpan->history(
3556             type => 'documentation',
3557             module => 'HTTP::Message',
3558             path => 'lib/HTTP/Message.pm',
3559             ) || die( $cpan->error );
3560              
3561             will find all C<documentation> history related to the module C<HTTP::Message>.
3562              
3563             This would return a L<Net::API::CPAN::List> object upon success.
3564              
3565             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fhistory%2Fdocumentation%2FHTTP%3A%3AMessage%2Flib%2FHTTP%2FMessage.pm> to see the data returned by the CPAN REST API.
3566              
3567             =back
3568              
3569             =head2 mirror
3570              
3571             my $list_obj = $cpan->mirror;
3572              
3573             This would return, upon success, a L<Net::API::CPAN::List> object.
3574              
3575             Actually there is no mirroring anymore, because for some time now CPAN runs on a CDN (Content Distributed Network) which performs the same result, but transparently.
3576              
3577             See more on this L<here|https://www.cpan.org/SITES.html>
3578              
3579             This endpoint also has search capability, but given there is now only one entry, it is completely useless.
3580              
3581             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fmirror> to see the data returned by the CPAN REST API.
3582              
3583             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3584              
3585             =head2 module
3586              
3587             # Queries modules with a simple search
3588             my $list_obj = $cpan->module(
3589             query => 'HTTP',
3590             from => 10,
3591             size => 10,
3592             ) || die( $cpan->error );
3593              
3594             # Queries modules with an advanced search using ElasticSearch
3595             my $list_obj = $cpan->module( $filter_object ) ||
3596             die( $cpan->error );
3597              
3598             # Retrieves the specified module information details
3599             my $module_obj = $cpan->module(
3600             module => 'HTTP::Message',
3601             ) || die( $cpan->error );
3602              
3603             # And if you want to join with other object types
3604             my $module_obj = $cpan->module(
3605             module => 'HTTP::Message',
3606             join => [qw( release author )],
3607             ) || die( $cpan->error );
3608              
3609             This method is used to query the CPAN REST API to retrieve C<module> information.
3610              
3611             =over
3612              
3613             =item * C<query> -> C</module>
3614              
3615             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</module>, such as:
3616              
3617             /module?q=HTTP
3618              
3619             For example:
3620              
3621             my $list_obj = $cpan->module( query => 'HTTP' ) ||
3622             die( $cpan->error );
3623              
3624             will find all C<modules> related to C<HTTP>.
3625              
3626             This would return a L<Net::API::CPAN::List> object upon success.
3627              
3628             The following options are also supported:
3629              
3630             =over 8
3631              
3632             =item * C<from>
3633              
3634             An integer representing the offset starting from 0 within the total data.
3635              
3636             =item * C<size>
3637              
3638             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3639              
3640             =back
3641              
3642             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fmodule%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3643              
3644             =item * L<search filter|Net::API::CPAN::Filter> -> C</module>
3645              
3646             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</module> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
3647              
3648             This would return a L<Net::API::CPAN::List> object upon success.
3649              
3650             =item * C<$module> -> C</module/{module}>
3651              
3652             If a string representing a C<module> is provided, this will be used to issue a query to the endpoint C</module/{module}> to retrieve the specified module information details, such as:
3653              
3654             /module/HTTP::Message
3655              
3656             For example:
3657              
3658             my $module_obj = $cpan->module(
3659             module => 'HTTP::Message',
3660             join => [qw( release author )],
3661             ) || die( $cpan->error );
3662              
3663             This would return, upon success, a L<Net::API::CPAN::Module> object.
3664              
3665             The following options are also supported:
3666              
3667             =over 8
3668              
3669             =item * C<join>
3670              
3671             You can join a.k.a. merge other objects data by setting C<join> to that object type, such as C<release> or C<author>. C<join> value can be either a string or an array of object types.
3672              
3673             =back
3674              
3675             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fmodule%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
3676              
3677             =back
3678              
3679             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3680              
3681             =head2 package
3682              
3683             # Queries packages with a simple search
3684             my $list_obj = $cpan->package(
3685             query => 'HTTP',
3686             from => 10,
3687             size => 10,
3688             ) || die( $cpan->error );
3689              
3690             # Queries packages with an advanced search using ElasticSearch
3691             my $list_obj = $cpan->package( $filter_object ) ||
3692             die( $cpan->error );
3693              
3694             # Retrieves the list of a distribution packages
3695             my $list_obj = $cpan->package( distribution => 'HTTP-Message' ) ||
3696             die( $cpan->error );
3697              
3698             # Retrieves the latest release and package information for the specified module
3699             my $package_obj = $cpan->package( 'HTTP::Message' ) ||
3700             die( $cpan->error );
3701              
3702             This method is used to query the CPAN REST API to retrieve C<package> information.
3703              
3704             =over 4
3705              
3706             =item * C<query> -> C</package>
3707              
3708             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</package>, such as:
3709              
3710             /package?q=HTTP
3711              
3712             For example:
3713              
3714             my $list_obj = $cpan->package( query => 'HTTP' ) ||
3715             die( $cpan->error );
3716              
3717             will find all C<packages> related to C<HTTP>.
3718              
3719             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Package>
3720              
3721             The following options are also supported:
3722              
3723             =over 8
3724              
3725             =item * C<from>
3726              
3727             An integer representing the offset starting from 0 within the total data.
3728              
3729             =item * C<size>
3730              
3731             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3732              
3733             =back
3734              
3735             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpackage%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3736              
3737             =item * L<search filter|Net::API::CPAN::Filter> -> C</package>
3738              
3739             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</package> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
3740              
3741             =item * C<distribution> -> C</package/modules/{distribution}>
3742              
3743             If the property C<distribution> is provided, this will issue a query to the endpoint C</package/modules/{distribution}> to retrieve the list of a distribution packages, such as:
3744              
3745             /package/modules/HTTP-Message
3746              
3747             For example:
3748              
3749             my $list_obj = $cpan->package( distribution => 'HTTP-Message' ) ||
3750             die( $cpan->error );
3751              
3752             This would return, upon success, an L<array object|Module::Generic::Array> containing all the modules name provided within the specified C<distribution>.
3753              
3754             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpackage%2Fmodules%2FHTTP-Message> to see the data returned by the CPAN REST API.
3755              
3756             =item * C<$package> -> C</package/{module}>
3757              
3758             If a string representing a package name is directly passed, this will issue a query to the endpoint C</package/{module}> to retrieve the latest release and package information for the specified module, such as:
3759              
3760             /package/HTTP::Message
3761              
3762             For example:
3763              
3764             my $package_obj = $cpan->package( 'HTTP::Message' ) ||
3765             die( $cpan->error );
3766              
3767             This would return, upon success, a L<Net::API::CPAN::Package> object.
3768              
3769             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpackage%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
3770              
3771             =back
3772              
3773             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3774              
3775             =head2 permission
3776              
3777             # Queries permissions with a simple search
3778             my $list_obj = $cpan->permission(
3779             query => 'HTTP',
3780             from => 10,
3781             size => 10,
3782             ) || die( $cpan->error );
3783              
3784             # Queries permissions with an advanced search using ElasticSearch
3785             my $list_obj = $cpan->permission( $filter_object ) ||
3786             die( $cpan->error );
3787              
3788             # Retrieves permission information details for the specified author
3789             my $list_obj = $cpan->permission(
3790             author => 'OALDERS',
3791             from => 40,
3792             size => 20,
3793             ) || die( $cpan->error );
3794              
3795             # Retrieves permission information details for the specified module
3796             my $list_obj = $cpan->permission(
3797             module => 'HTTP::Message',
3798             ) || die( $cpan->error );
3799              
3800             # Retrieves permission information details for the specified modules
3801             my $list_obj = $cpan->permission(
3802             module => [qw( HTTP::Message Data::HexDump )],
3803             ) || die( $cpan->error );
3804              
3805             This method is used to query the CPAN REST API to retrieve C<package> information.
3806              
3807             =over 4
3808              
3809             =item * C<query> -> C</permission>
3810              
3811             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</permission>, such as:
3812              
3813             /permission?q=HTTP
3814              
3815             For example:
3816              
3817             my $list_obj = $cpan->permission(
3818             query => 'HTTP',
3819             from => 10,
3820             size => 10,
3821             ) || die( $cpan->error );
3822              
3823             will find all C<permissions> related to C<HTTP>.
3824              
3825             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Permission> objects.
3826              
3827             The following options are also supported:
3828              
3829             =over 8
3830              
3831             =item * C<from>
3832              
3833             An integer representing the offset starting from 0 within the total data.
3834              
3835             =item * C<size>
3836              
3837             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
3838              
3839             =back
3840              
3841             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpermission%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
3842              
3843             =item * L<search filter|Net::API::CPAN::Filter> -> C</permission>
3844              
3845             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</permission> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
3846              
3847             =item * C<author> -> C</permission/by_author/{author}>
3848              
3849             If the property C<author> is provided, this will trigger a simple search query to the endpoint C</permission/by_author/{author}> to retrieve the permission information details for the specified author, such as:
3850              
3851             /permission/by_author/OALDERS?from=40&q=HTTP&size=20
3852              
3853             For example:
3854              
3855             my $list_obj = $cpan->permission(
3856             author => 'OALDERS',
3857             from => 40,
3858             size => 20,
3859             ) || die( $cpan->error );
3860              
3861             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Permission> objects.
3862              
3863             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpermission%2Fby_author%2FOALDERS%3Ffrom%3D40%26q%3DHTTP%26size%3D20> to see the data returned by the CPAN REST API.
3864              
3865             =item * C<module> -> C</permission/{module}>
3866              
3867             If the property C<module> is provided, and its value is a string, this will issue a query to the endpoint C</permission/{module}> to retrieve permission information details for the specified module, such as:
3868              
3869             /permission/HTTP::Message
3870              
3871             For example:
3872              
3873             my $list_obj = $cpan->permission(
3874             module => 'HTTP::Message',
3875             ) || die( $cpan->error );
3876              
3877             This would return, upon success, a L<Net::API::CPAN::Permission> object.
3878              
3879             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpermission%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
3880              
3881             =item * [C<module>] -> C</permission/by_module>
3882              
3883             If the property C<module> is provided, and its value is an array reference, this will issue a query to the endpoint C</permission/by_module> to retrieve permission information details for the specified modules, such as:
3884              
3885             /permission/by_module?module=HTTP%3A%3AMessage&module=Data%3A%3AHexDump
3886              
3887             For example:
3888              
3889             my $list_obj = $cpan->permission(
3890             module => [qw( HTTP::Message Data::HexDump )],
3891             ) || die( $cpan->error );
3892              
3893             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Permission> objects.
3894              
3895             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpermission%2Fby_module%3Fmodule%3DHTTP%253A%253AMessage%26module%3DData%253A%253AHexDump> to see the data returned by the CPAN REST API.
3896              
3897             =back
3898              
3899             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
3900              
3901             =head2 pod
3902              
3903             # Returns the POD of the given module in the
3904             # specified release in markdown format
3905             my $string = $cpan->pod(
3906             author => 'OALDERS',
3907             release => 'HTTP-Message-6.36',
3908             path => 'lib/HTTP/Message.pm',
3909             accept => 'text/x-markdown',
3910             ) || die( $cpan->error );
3911              
3912             # Returns the POD of the given module in
3913             # markdown format
3914             my $string = $cpan->pod(
3915             module => 'HTTP::Message',
3916             accept => 'text/x-markdown',
3917             ) || die( $cpan->error );
3918              
3919             # Renders the specified POD code into HTML
3920             my $html = $cpan->pod(
3921             render => qq{=encoding utf-8\n\n=head1 Hello World\n\nSomething here\n\n=oops\n\n=cut\n}
3922             ) || die( $cpan->error );
3923              
3924             This method is used to query the CPAN REST API to retrieve C<pod> documentation from specified modules and to render pod into C<HTML> data.
3925              
3926             =over 4
3927              
3928             =item * C<author>, C<release> and C<path> -> C</pod/{author}/{release}/{path}>
3929              
3930             If the properties C<author>, C<release> and C<path> are provided, this will issue a query to the endpoint C</pod/{author}/{release}/{path}> to retrieve the POD of the given module in the specified release, such as:
3931              
3932             /pod/OALDERS/HTTP-Message-6.36/lib/HTTP/Message.pm
3933              
3934             For example:
3935              
3936             my $string = $cpan->pod(
3937             author => 'OALDERS',
3938             release => 'HTTP-Message-6.36',
3939             path => 'lib/HTTP/Message.pm',
3940             accept => 'text/x-markdown',
3941             ) || die( $cpan->error );
3942              
3943             This would return a string of data in the specified format, which can be one of C<text/html>, C<text/plain>, C<text/x-markdown> or C<text/x-pod>. By default this is C<text/html>. The preferred data type is specified with the property C<accept>
3944              
3945             The following options are also supported:
3946              
3947             =over 8
3948              
3949             =item * C<accept>
3950              
3951             This value instructs the MetaCPAN API to return the pod data in the desired format.
3952              
3953             Supported formats are: C<text/html>, C<text/plain>, C<text/x-markdown>, C<text/x-pod>
3954              
3955             =back
3956              
3957             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpod%2FOALDERS%2FHTTP-Message-6.36%2Flib%2FHTTP%2FMessage.pm> to see the data returned by the CPAN REST API.
3958              
3959             =item * C<module> -> C</v1/pod/{module}>
3960              
3961             If the property C<module> is provided, this will issue a query to the endpoint C</v1/pod/{module}> to retrieve the POD of the specified module, such as:
3962              
3963             /pod/HTTP::Message
3964              
3965             For example:
3966              
3967             my $string = $cpan->pod(
3968             module => 'HTTP::Message',
3969             accept => 'text/x-markdown',
3970             ) || die( $cpan->error );
3971              
3972             Just like the previous one, this would return a string of data in the specified format (in the above example markdown), which can be one of C<text/html>, C<text/plain>, C<text/x-markdown> or C<text/x-pod>. By default this is C<text/html>. The preferred data type is specified with the property C<accept>.
3973              
3974             The following options are also supported:
3975              
3976             =over 8
3977              
3978             =item * C<accept>
3979              
3980             This value instructs the MetaCPAN API to return the pod data in the desired format.
3981              
3982             Supported formats are: C<text/html>, C<text/plain>, C<text/x-markdown>, C<text/x-pod>
3983              
3984             =back
3985              
3986             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpod%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
3987              
3988             =item * C<render> -> C</pod_render>
3989              
3990             If the property C<render> is provided with a string of C<POD> data, this will issue a query to the endpoint C</pod_render>, such as:
3991              
3992             /pod_render?pod=%3Dencoding+utf-8%0A%0A%3Dhead1+Hello+World%0A%0ASomething+here%0A%0A%3Doops%0A%0A%3Dcut%0A
3993              
3994             For example:
3995              
3996             my $html = $cpan->pod(
3997             render => qq{=encoding utf-8\n\n=head1 Hello World\n\nSomething here\n\n=oops\n\n=cut\n}
3998             ) || die( $cpan->error );
3999              
4000             This would return a string of C<HTML> formatted data.
4001              
4002             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fpod_render%3Fpod%3D%253Dencoding%2Butf-8%250A%250A%253Dhead1%2BHello%2BWorld%250A%250ASomething%2Bhere%250A%250A%253Doops%250A%250A%253Dcut%250A> to see the data returned by the CPAN REST API.
4003              
4004             =back
4005              
4006             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4007              
4008             =head2 rating
4009              
4010             # Queries permissions with a simple search
4011             my $list_obj = $cpan->rating(
4012             query => 'HTTP',
4013             from => 10,
4014             size => 10,
4015             ) || die( $cpan->error );
4016              
4017             # Queries permissions with an advanced search using ElasticSearch format
4018             my $list_obj = $cpan->rating( $filter_object ) ||
4019             die( $cpan->error );
4020              
4021             # Retrieves rating information details of the specified distribution
4022             my $list_obj = $cpan->rating(
4023             distribution => 'HTTP-Tiny',
4024             ) || die( $cpan->error );
4025              
4026             This method is used to query the CPAN REST API to retrieve C<rating> historical data for the specified search query or C<distribution>.
4027              
4028             It is worth mentioning that although this endpoint still works, CPAN Ratings has been decommissioned some time ago, and thus its usefulness is questionable.
4029              
4030             =over 4
4031              
4032             =item * C<query> -> C</rating>
4033              
4034             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</rating>, such as:
4035              
4036             /rating?q=HTTP
4037              
4038             For example:
4039              
4040             my $list_obj = $cpan->rating(
4041             query => 'HTTP',
4042             from => 10,
4043             size => 10,
4044             ) || die( $cpan->error );
4045              
4046             will find all C<ratings> related to C<HTTP>.
4047              
4048             This would return a L<Net::API::CPAN::List> object upon success.
4049              
4050             The following options are also supported:
4051              
4052             =over 8
4053              
4054             =item * C<from>
4055              
4056             An integer representing the offset starting from 0 within the total data.
4057              
4058             =item * C<size>
4059              
4060             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4061              
4062             =back
4063              
4064             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frating%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
4065              
4066             =item * L<search filter|Net::API::CPAN::Filter> -> C</rating>
4067              
4068             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</rating> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
4069              
4070             =item * C<distribution> -> C</rating/by_distributions>
4071              
4072             If a property C<distribution> is provided, this will issue a query to the endpoint C</rating/by_distributions> to retrieve rating information details of the specified distribution, such as:
4073              
4074             /rating/by_distributions?distribution=HTTP-Tiny
4075              
4076             For example:
4077              
4078             my $list_obj = $cpan->rating(
4079             distribution => 'HTTP-Tiny',
4080             ) || die( $cpan->error );
4081              
4082             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Rating> objects.
4083              
4084             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frating%2Fby_distributions%3Fdistribution%3DHTTP-Tiny> to see the data returned by the CPAN REST API.
4085              
4086             =back
4087              
4088             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4089              
4090             =head2 release
4091              
4092             # Perform a simple query
4093             my $list_obj = $cpan->release(
4094             query => 'HTTP',
4095             from => 10,
4096             size => 10,
4097             ) || die( $cpan->error );
4098              
4099             # Perform an advanced query using ElasticSearch format
4100             my $list_obj = $cpan->release( $filter_object ) ||
4101             die( $cpan->error );
4102              
4103             # Retrieves a list of all releases for a given author
4104             my $list_obj = $cpan->release(
4105             all => 'OALDERS',
4106             page => 2,
4107             size => 100,
4108             ) || die( $cpan->error );
4109              
4110             # Retrieves a shorter list of all releases for a given author
4111             my $list_obj = $cpan->release( author => 'OALDERS' ) ||
4112             die( $cpan->error );
4113              
4114             # Retrieve a release information details
4115             my $release_obj = $cpan->release(
4116             author => 'OALDERS',
4117             release => 'HTTP-Message-6.36',
4118             ) || die( $cpan->error );
4119              
4120             # Retrieves the latest distribution release information details
4121             my $release_obj = $cpan->release(
4122             distribution => 'HTTP-Message',
4123             ) || die( $cpan->error );
4124              
4125             # Retrieves the list of contributors for the specified distributions
4126             my $list_obj = $cpan->release(
4127             author => 'OALDERS',
4128             release => 'HTTP-Message-6.36',
4129             contributors => 1,
4130             ) || die( $cpan->error );
4131              
4132             # Retrieves the list of release key files by category
4133             my $hash_ref = $cpan->release(
4134             author => 'OALDERS',
4135             release => 'HTTP-Message-6.36',
4136             files => 1,
4137             ) || die( $cpan->error );
4138              
4139             # Retrieves the list of interesting files for the given release
4140             my $list_obj = $cpan->release(
4141             author => 'OALDERS',
4142             release => 'HTTP-Message-6.36',
4143             # You can also use just 'interesting'
4144             interesting_files => 1,
4145             ) || die( $cpan->error );
4146              
4147             # Get latest releases by the specified author
4148             my $list_obj = $cpan->release(
4149             author => 'OALDERS',
4150             latest => 1,
4151             ) || die( $cpan->error );
4152              
4153             # Get the latest releases for the specified distribution
4154             my $release_obj = $cpan->release(
4155             distribution => 'HTTP-Message',
4156             latest => 1,
4157             ) || die( $cpan->error );
4158              
4159             # Retrieves the list of modules in the specified release
4160             my $list_obj = $cpan->release(
4161             author => 'OALDERS',
4162             release => 'HTTP-Message-6.36',
4163             modules => 1,
4164             ) || die( $cpan->error );
4165              
4166             # Get the list of recent releases
4167             my $list_obj = $cpan->release(
4168             recent => 1,
4169             ) || die( $cpan->error );
4170              
4171             # get all releases by versions for the specified distribution
4172             my $list_obj = $cpan->release(
4173             versions => 'HTTP-Message',
4174             ) || die( $cpan->error );
4175              
4176             This method is used to query the CPAN REST API to retrieve C<release> information.
4177              
4178             =over 4
4179              
4180             =item * C<query> -> C</release>
4181              
4182             If the property C<query> is provided, this will trigger a simple search query to the endpoint C</release>, such as:
4183              
4184             /release?q=HTTP
4185              
4186             For example:
4187              
4188             my $list_obj = $cpan->release(
4189             query => 'HTTP',
4190             from => 10,
4191             size => 10,
4192             ) || die( $cpan->error );
4193              
4194             will find all C<releases> related to C<HTTP>.
4195              
4196             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4197              
4198             The following options are also supported:
4199              
4200             =over 8
4201              
4202             =item * C<from>
4203              
4204             An integer representing the offset starting from 0 within the total data.
4205              
4206             =item * C<size>
4207              
4208             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4209              
4210             =back
4211              
4212             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
4213              
4214             =item * L<search filter|Net::API::CPAN::Filter> -> C</release>
4215              
4216             And if a L<search filter|Net::API::CPAN::Filter> is passed, this will trigger a more advanced ElasticSearch query to the endpoint C</release> using the C<HTTP> C<POST> method. See the L<Net::API::CPAN::Filter> module on more details on what granular queries you can execute.
4217              
4218             =item * C<all> -> C</release/all_by_author/{author}>
4219              
4220             If the property C<all> is provided, this will issue a query to the endpoint C</release/all_by_author/{author}> to get all releases by the specified author, such as:
4221              
4222             /release/all_by_author/OALDERS?page=2&page_size=100
4223              
4224             For example:
4225              
4226             my $list_obj = $cpan->release(
4227             all => 'OALDERS',
4228             page => 2,
4229             size => 100,
4230             ) || die( $cpan->error );
4231              
4232             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4233              
4234             The following options are also supported:
4235              
4236             =over 8
4237              
4238             =item * C<page>
4239              
4240             An integer representing the page offset starting from 1.
4241              
4242             =item * C<size>
4243              
4244             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4245              
4246             =back
4247              
4248             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Fall_by_author%2FOALDERS%3Fpage%3D1%26page_size%3D100> to see the data returned by the CPAN REST API.
4249              
4250             =item * C<author> -> C</release/by_author/{author}>
4251              
4252             If the property C<author> alone is provided, this will issue a query to the endpoint C</release/by_author/{author}> to get releases by author, such as:
4253              
4254             /release/by_author/OALDERS
4255              
4256             For example:
4257              
4258             my $list_obj = $cpan->release( author => 'OALDERS' ) ||
4259             die( $cpan->error );
4260              
4261             This would return a L<Net::API::CPAN::List> object upon success.
4262              
4263             Note that this is similar to C<all>, but returns a subset of all the author's data.
4264              
4265             The following options are also supported:
4266              
4267             =over 8
4268              
4269             =item * C<page>
4270              
4271             An integer representing the page offset starting from 1.
4272              
4273             =item * C<size>
4274              
4275             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4276              
4277             =back
4278              
4279             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Fby_author%2FOALDERS> to see the data returned by the CPAN REST API.
4280              
4281             =item * C<author> and C<release> -> C</v1/release/{author}/{release}>
4282              
4283             If the property C<author> and C<release> are provided, this will issue a query to the endpoint C</v1/release/{author}/{release}> tp retrieve a distribution release information, such as:
4284              
4285             /release/OALDERS/HTTP-Message-6.36
4286              
4287             For example:
4288              
4289             my $release_obj = $cpan->release(
4290             author => 'OALDERS',
4291             release => 'HTTP-Message-6.36',
4292             ) || die( $cpan->error );
4293              
4294             This would return a L<Net::API::CPAN::Release> object upon success.
4295              
4296             The following options are also supported:
4297              
4298             =over 8
4299              
4300             =item * C<join>
4301              
4302             You can join a.k.a. merge other objects data by setting C<join> to that object type, such as C<module> or C<author>. C<join> value can be either a string or an array of object types.
4303              
4304             =back
4305              
4306             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
4307              
4308             =item * C<distribution> -> C</release/{distribution}>
4309              
4310             If the property C<distribution> alone is provided, this will issue a query to the endpoint C</release/{distribution}> to retrieve a release information details., such as:
4311              
4312             /release/HTTP-Message
4313              
4314             For example:
4315              
4316             my $release_obj = $cpan->release(
4317             distribution => 'HTTP-Message',
4318             ) || die( $cpan->error );
4319              
4320             This would return a L<Net::API::CPAN::Release> object upon success.
4321              
4322             The following options are also supported:
4323              
4324             =over 8
4325              
4326             =item * C<join>
4327              
4328             You can join a.k.a. merge other objects data by setting C<join> to that object type, such as C<module> or C<author>. C<join> value can be either a string or an array of object types.
4329              
4330             =back
4331              
4332             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2FHTTP-Message> to see the data returned by the CPAN REST API.
4333              
4334             =item * C<contributors>, C<author> and C<release> -> C</release/contributors/{author}/{release}>
4335              
4336             If the property C<contributors>, C<author> and C<release> are provided, this will issue a query to the endpoint C</release/contributors/{author}/{release}> to retrieve the list of contributors for the specified release, such as:
4337              
4338             /release/contributors/OALDERS/HTTP-Message-6.36
4339              
4340             For example:
4341              
4342             my $list_obj = $cpan->release(
4343             author => 'OALDERS',
4344             release => 'HTTP-Message-6.36',
4345             contributors => 1,
4346             ) || die( $cpan->error );
4347              
4348             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4349             :List> object upon success.
4350              
4351             The following options are also supported:
4352              
4353             =over 8
4354              
4355             =item * C<join>
4356              
4357             You can join a.k.a. merge other objects data by setting C<join> to that object type, such as C<module> or C<author>. C<join> value can be either a string or an array of object types.
4358              
4359             =back
4360              
4361             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Fcontributors%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
4362              
4363             =item * C<files>, C<author> and C<release> -> C</release/files_by_category/{author}/{release}>
4364              
4365             If the property C<files>, C<author> and C<release> are provided, this will issue a query to the endpoint C</release/files_by_category/{author}/{release}> to retrieve the list of key files by category for the specified release, such as:
4366              
4367             /release/files_by_category/OALDERS/HTTP-Message-6.36
4368              
4369             For example:
4370              
4371             my $hash_ref = $cpan->release(
4372             author => 'OALDERS',
4373             release => 'HTTP-Message-6.36',
4374             files => 1,
4375             ) || die( $cpan->error );
4376              
4377             This would return an L<hash object|Module::Generic::Hash> of the following category names, each having, as their value, an array of the specified C<release> files.
4378              
4379             The categories are:
4380              
4381             =over 8
4382              
4383             =item * C<changelog>
4384              
4385             This is typically the C<Changes> or C<CHANGES> file.
4386              
4387             =item * C<contributing>
4388              
4389             This is typically the C<CONTRIBUTING.md> file.
4390              
4391             =item * C<dist>
4392              
4393             This is typically other files that are part of the C<release>, such as C<cpanfile>, C<Makefile.PL>, C<dist.ini>, C<META.json>, C<META.yml>, C<MANIFEST>.
4394              
4395             =item * C<install>
4396              
4397             This is typically the C<INSTALL> file.
4398              
4399             =item * C<license>
4400              
4401             This is typically the C<LICENSE> file.
4402              
4403             =item * C<other>
4404              
4405             This is typically the C<README.md> file.
4406              
4407             =back
4408              
4409             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Ffiles_by_category%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
4410              
4411             =item * C<interesting_files>, C<author> and C<release> -> C</release/interesting_files/{author}/{release}>
4412              
4413             If the property C<interesting_files> (or also just C<interesting>), C<author> and C<release> are provided, this will issue a query to the endpoint C</release/interesting_files/{author}/{release}> to retrieve the list of release interesting files for the specified release, such as:
4414              
4415             /release/interesting_files/OALDERS/HTTP-Message-6.36
4416              
4417             For example:
4418              
4419             my $list_obj = $cpan->release(
4420             author => 'OALDERS',
4421             release => 'HTTP-Message-6.36',
4422             interesting_files => 1,
4423             ) || die( $cpan->error );
4424              
4425             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4426              
4427             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Finteresting_files%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
4428              
4429             =item * C<latest>, and C<author> -> C</release/latest_by_author/{author}>
4430              
4431             If the property C<latest>, and C<author> are provided, this will issue a query to the endpoint C</release/latest_by_author/{author}> to retrieve the latest releases by the specified author, such as:
4432              
4433             /release/latest_by_author/OALDERS
4434              
4435             For example:
4436              
4437             my $list_obj = $cpan->release(
4438             author => 'OALDERS',
4439             latest => 1,
4440             ) || die( $cpan->error );
4441              
4442             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4443              
4444             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Flatest_by_author%2FOALDERS> to see the data returned by the CPAN REST API.
4445              
4446             =item * C<latest>, and C<distribution> -> C</release/latest_by_distribution/{distribution}>
4447              
4448             If the property C<latest>, and C<distribution> are provided, this will issue a query to the endpoint C</release/latest_by_distribution/{distribution}> to retrieve the latest releases of the specified distribution, such as:
4449              
4450             /release/latest_by_distribution/HTTP-Message
4451              
4452             For example:
4453              
4454             my $release_obj = $cpan->release(
4455             distribution => 'HTTP-Message',
4456             latest => 1,
4457             ) || die( $cpan->error );
4458              
4459             This would return, upon success, a L<Net::API::CPAN::Release> object representing the latest C<release> for the specified C<distribution>.
4460              
4461             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Flatest_by_distribution%2FHTTP-Message> to see the data returned by the CPAN REST API.
4462              
4463             =item * C<modules>, C<author>, and C<release> -> C</release/modules/{author}/{release}>
4464              
4465             If the property C<modules>, C<author>, and C<release> are provided, this will issue a query to the endpoint C</release/modules/{author}/{release}> to retrieve the list of modules in the specified distribution, such as:
4466              
4467             /release/modules/OALDERS/HTTP-Message-6.36
4468              
4469             For example:
4470              
4471             my $list_obj = $cpan->release(
4472             author => 'OALDERS',
4473             release => 'HTTP-Message-6.36',
4474             modules => 1,
4475             ) || die( $cpan->error );
4476              
4477             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4478              
4479             The following options are also supported:
4480              
4481             =over 8
4482              
4483             =item * C<join>
4484              
4485             You can join a.k.a. merge other objects data by setting C<join> to that object type, such as C<module> or C<author>. C<join> value can be either a string or an array of object types.
4486              
4487             =back
4488              
4489             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Fmodules%2FOALDERS%2FHTTP-Message-6.36> to see the data returned by the CPAN REST API.
4490              
4491             =item * C<recent> -> C</release/recent>
4492              
4493             If the property C<recent>, alone is provided, this will issue a query to the endpoint C</release/recent> to retrieve the list of recent releases, such as:
4494              
4495             /release/recent
4496              
4497             For example:
4498              
4499             my $list_obj = $cpan->release(
4500             recent => 1,
4501             ) || die( $cpan->error );
4502              
4503             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4504              
4505             The following options are also supported:
4506              
4507             =over 8
4508              
4509             =item * C<page>
4510              
4511             An integer specifying the page offset starting from 1.
4512              
4513             =item * C<size>
4514              
4515             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4516              
4517             =back
4518              
4519             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Frecent> to see the data returned by the CPAN REST API.
4520              
4521             =item * C<versions> -> C<distribution>
4522              
4523             If the property C<versions> is provided having a value representing a C<distribution>, this will issue a query to the endpoint C</release/versions/{distribution}> to retrieve all releases by versions for the specified distribution, such as:
4524              
4525             /release/versions/HTTP-Message
4526              
4527             For example:
4528              
4529             my $list_obj = $cpan->release(
4530             distribution => 'HTTP-Message',
4531             # or, alternatively: version => '6.35,6.36,6.34',
4532             versions => [qw( 6.35 6.36 6.34 )],
4533             # Set this to true to get a raw list of version -> download URL instead of a list object
4534             # plain => 1,
4535             ) || die( $cpan->error );
4536              
4537             This would return, upon success, a L<Net::API::CPAN::List> object of all the C<distribution> versions released.
4538              
4539             The following options are also supported:
4540              
4541             =over 8
4542              
4543             =item * C<versions>
4544              
4545             An array reference of versions to return, or a string specifying the version(s) to return as a comma-sepated value
4546              
4547             =item * C<plain>
4548              
4549             A boolean value specifying whether the result should be returned in plain mode.
4550              
4551             =back
4552              
4553             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Fversions%2FHTTP-Message> to see the data returned by the CPAN REST API and L<here for the result in plain text mode|https://explorer.metacpan.org/?url=%2Frelease%2Fversions%2FHTTP-Message%3Fplain%3D1>.
4554              
4555             =back
4556              
4557             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4558              
4559             =head2 reverse
4560              
4561             # Returns a list of all the modules who depend on the specified distribution
4562             my $list_obj = $cpan->reverse( distribution => 'HTTP-Message' ) ||
4563             die( $cpan->error );
4564              
4565             # Returns a list of all the modules who depend on the specified module
4566             my $list_obj = $cpan->reverse( module => 'HTTP::Message' ) ||
4567             die( $cpan->error );
4568              
4569             This method is used to query the CPAN REST API to retrieve reverse dependencies, i.e. releases on C<CPAN> that depend on the specified C<distribution> or C<module>.
4570              
4571             =over 4
4572              
4573             =item * C<distribution> -> C</reverse_dependencies/dist/{distribution}>
4574              
4575             If the property C<distribution> representing a distribution is provided, this will issue a query to the endpoint C</reverse_dependencies/dist/{distribution}> to retrieve a list of all the modules who depend on the specified distribution, such as:
4576              
4577             /reverse_dependencies/dist/HTTP-Message
4578              
4579             For example:
4580              
4581             my $list_obj = $cpan->reverse( distribution => 'HTTP-Message' ) ||
4582             die( $cpan->error );
4583              
4584             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4585              
4586             The following options are also supported:
4587              
4588             =over 8
4589              
4590             =item * C<page>
4591              
4592             An integer representing the page offset starting from 1.
4593              
4594             =item * C<size>
4595              
4596             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4597              
4598             =item * C<sort>
4599              
4600             A string representing a field specifying how the result is sorted.
4601              
4602             =back
4603              
4604             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Freverse_dependencies%2Fdist%2FHTTP-Message> to see the data returned by the CPAN REST API.
4605              
4606             =item * C<module> -> C</reverse_dependencies/module/{module}>
4607              
4608             If the property C<module> representing a module is provided, this will issue a query to the endpoint C</reverse_dependencies/module/{module}> to retrieve a list of all the modules who depend on the specified module, such as:
4609              
4610             /reverse_dependencies/module/HTTP::Message
4611              
4612             For example:
4613              
4614             my $list_obj = $cpan->reverse( module => 'HTTP::Message' ) ||
4615             die( $cpan->error );
4616              
4617             This would return, upon success, a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release> objects.
4618              
4619             The following options are also supported:
4620              
4621             =over 8
4622              
4623             =item * C<page>
4624              
4625             An integer representing the page offset starting from 1.
4626              
4627             =item * C<size>
4628              
4629             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4630              
4631             =item * C<sort>
4632              
4633             A string representing a field specifying how the result is sorted.
4634              
4635             =back
4636              
4637             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Freverse_dependencies%2Fmodule%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
4638              
4639             =back
4640              
4641             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4642              
4643             =head2 reverse_dependencies
4644              
4645             This is an alias for L</reverse>
4646              
4647             =head2 search
4648              
4649             Provided with an hash or hash reference of options and this performs a search query and returns a L<Net::API::CPAN::List> object, or an L<Net::API::CPAN::Scroll> depending on the type of search query requested.
4650              
4651             There are 3 types of search query:
4652              
4653             =over 4
4654              
4655             =item 1. Using L<HTTP GET method|https://github.com/metacpan/metacpan-api/blob/master/docs/API-docs.md#get-searches>
4656              
4657             =item 2. Using L<HTTP POST method|https://github.com/metacpan/metacpan-api/blob/master/docs/API-docs.md#post-searches> with L<Elastic Search query|Net::API::CPAN::Filter>
4658              
4659             =item 3. Using L<HTTP POST method|https://github.com/metacpan/metacpan-api/blob/master/docs/API-docs.md#post-searches> with Elastic Search query using L<scroll|Net::API::CPAN::Scroll>
4660              
4661             =back
4662              
4663             =head2 source
4664              
4665             # Retrieves the source code of the given module path within the specified release
4666             my $string = $cpan->source(
4667             author => 'OALDERS',
4668             release => 'HTTP-Message-6.36',
4669             path => 'lib/HTTP/Message.pm',
4670             ) || die( $cpan->error );
4671              
4672             # Retrieves the full source of the latest, authorized version of the specified module
4673             my $string = $cpan->source( module => 'HTTP::Message' ) ||
4674             die( $cpan->error );
4675              
4676             This method is used to query the CPAN REST API to retrieve the source code or data of the specified C<release> element or C<module>.
4677              
4678             =over 4
4679              
4680             =item * C<author>, C<release> and C<path> -> C</source/{author}/{release}/{path}>
4681              
4682             If the properties C<author>, C<release> and C<path> are provided, this will issue a query to the endpoint C</source/{author}/{release}/{path}> to retrieve the source code of the given module path within the specified release, such as:
4683              
4684             /source/OALDERS/HTTP-Message-6.36/lib/HTTP/Message.pm
4685              
4686             For example:
4687              
4688             my $string = $cpan->source(
4689             author => 'OALDERS',
4690             release => 'HTTP-Message-6.36',
4691             path => 'lib/HTTP/Message.pm',
4692             ) || die( $cpan->error );
4693              
4694             This will return a string representing the source data of the file located at the specified C<path> and C<release>.
4695              
4696             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsource%2FOALDERS%2FHTTP-Message-6.36%2Flib%2FHTTP%2FMessage.pm> to see the data returned by the CPAN REST API.
4697              
4698             =item * C<module> -> C</source/{module}>
4699              
4700             If the properties C<module> is provided, this will issue a query to the endpoint C</source/{module}> to retrieve the full source of the latest, authorized version of the specified module, such as:
4701              
4702             /source/HTTP::Message
4703              
4704             For example:
4705              
4706             my $string = $cpan->source( module => 'HTTP::Message' ) ||
4707             die( $cpan->error );
4708              
4709             This will return a string representing the source data of the specified C<module>.
4710              
4711             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsource%2FHTTP%3A%3AMessage> to see the data returned by the CPAN REST API.
4712              
4713             =back
4714              
4715             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4716              
4717             =head2 suggest
4718              
4719             This takes a string and will issue a query to the endpoint C</search/autocomplete/suggest> to retrieve the suggested result set based on the autocomplete search query, such as:
4720              
4721             /search/autocomplete/suggest?q=HTTP
4722              
4723             For example:
4724              
4725             my $list_obj = $cpan->suggest( query => 'HTTP' ) || die( $cpan->error );
4726              
4727             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Release::Suggest> objects.
4728              
4729             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fautocomplete%2Fsuggest%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
4730              
4731             =head2 top_uploaders
4732              
4733             This will issue a query to the endpoint C</release/top_uploaders> to retrieve an L<hash object|Module::Generic::Hash> of the top uploading C<authors> with the total as the key's value, such as:
4734              
4735             /release/top_uploaders
4736              
4737             For example:
4738              
4739             my $hash_ref = $cpan->top_uploaders || die( $cpan->error );
4740              
4741             This would return, upon success, an L<hash object|Module::Generic::Hash> of C<author> and their recent total number of C<release> upload on C<CPAN>
4742              
4743             For example:
4744              
4745             {
4746             OALDERS => 12,
4747             NEILB => 7,
4748             }
4749              
4750             The following options are also supported:
4751              
4752             =over 8
4753              
4754             =item * C<range>
4755              
4756             A string specifying the result range. Valid values are C<all>, C<weekly>, C<monthly> or C<yearly>. It defaults to C<weekly>
4757              
4758             =item * C<size>
4759              
4760             An integer representing the size of each page, i.e. how many results are returned per page. This usually defaults to 10.
4761              
4762             =back
4763              
4764             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Frelease%2Ftop_uploaders> to see the data returned by the CPAN REST API.
4765              
4766             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4767              
4768             =head2 web
4769              
4770             This takes a string and will issue a query to the endpoint C</search/web> to retrieve the result set based on the search query specified similar to the one on the MetaCPAN website, such as:
4771              
4772             /search/web?q=HTTP
4773              
4774             For example:
4775              
4776             my $list_obj = $cpan->web(
4777             query => 'HTTP',
4778             from => 0,
4779             size => 10,
4780             ) || die( $cpan->error );
4781              
4782             This would, upon success, return a L<Net::API::CPAN::List> object of L<Net::API::CPAN::Module> objects.
4783              
4784             Search terms can be:
4785              
4786             =over 8
4787              
4788             =item * can be unqualified string, such as C<paging>
4789              
4790             =item * can be author, such as C<author:OALDERS>
4791              
4792             =item * can be module, such as C<module:HTTP::Message>
4793              
4794             =item * can be distribution, such as C<dist:HTTP-Message>
4795              
4796             =back
4797              
4798             The following options are also supported:
4799              
4800             =over 8
4801              
4802             =item * C<collapsed>
4803              
4804             Boolean. When used, this forces a collapsed even when searching for a particular distribution or module name.
4805              
4806             =item * C<from>
4807              
4808             An integer that represents offset to use in the result set.
4809              
4810             =item * C<size>
4811              
4812             An integer that represents the number of results per page.
4813              
4814             =back
4815              
4816             You can try it out on L<CPAN Explorer|https://explorer.metacpan.org/?url=%2Fsearch%2Fweb%3Fq%3DHTTP> to see the data returned by the CPAN REST API.
4817              
4818             Upon failure, an L<error|Net::API::CPAN::Exception> will be set and C<undef> will be returned in scalar context, or an empty list in list context.
4819              
4820             =head1 TERMINOLOGY
4821              
4822             The MetaCPAN REST API has quite a few endpoints returning sets of data containing properties. Below are the meanings of some of those keywords:
4823              
4824             =over 4
4825              
4826             =item * C<author>
4827              
4828             For example C<JOHNDOE>
4829              
4830             This is a C<CPAN> id, and C<distribution> author. It is also referred as C<cpanid>
4831              
4832             =item * C<cpanid>
4833              
4834             For example C<JOHNDOE>
4835              
4836             See C<author>
4837              
4838             =item * C<contributor>
4839              
4840             For example: C<JOHNDOE>
4841              
4842             A C<contributor> is a C<CPAN> author who is contributing code to an C<author>'s C<distribution>.
4843              
4844             =item * C<distribution>
4845              
4846             For example: C<HTTP-Message>
4847              
4848             This is a bundle of modules distributed over C<CPAN> and available for download. A C<distribution> goes through a series of C<releases> over the course of its lifetime.
4849              
4850             =item * C<favorite>
4851              
4852             C<favorite> relates to the appreciation a C<distribution> received by having registered and non-registered user marking it as one of their favorite distributions.
4853              
4854             =item * C<file>
4855              
4856             A C<file> is an element of a C<distribution>
4857              
4858             =item * C<module>
4859              
4860             For example C<HTTP::Message>
4861              
4862             This has the same meaning as in Perl. See L<perlmod> for more information on Perl modules.
4863              
4864             =item * C<package>
4865              
4866             For example C<HTTP::Message>
4867              
4868             This is similar to C<module>, but a C<package> is a C<class> and a C<module> is a file.
4869              
4870             =item * C<permission>
4871              
4872             A C<permission> defines the role a user has over a C<distribution> and is one of C<owner> or C<co_maintainer>
4873              
4874             =item * C<release>
4875              
4876             For example: C<HTTP-Message-6.36>
4877              
4878             A C<release> is a C<distribution> being released with a unique version number.
4879              
4880             =item * C<reverse_dependencies>
4881              
4882             This relates to the C<distributions> depending on any given C<distribution>
4883              
4884             =back
4885              
4886             =head1 ERRORS
4887              
4888             This module does not die or croak, but instead set an L<error object|Net::API::CPAN::Exception> using L<Module::Generic/error> and returns C<undef> in scalar context, or an empty list in list context.
4889              
4890             You can retrieve the latest error object set by calling L<error|Module::Generic/error> inherited from L<Module::Generic>
4891              
4892             Errors issued by this distributions are all instances of class L<Net::API::CPAN::Exception>
4893              
4894             =head1 METACPAN OPENAPI SPECIFICATIONS
4895              
4896             From the information I could gather, L<I have produced the specifications|https://gitlab.com/jackdeguest/Net-API-CPAN/-/blob/master/build/cpan-openapi-spec-3.0.0.pl> for L<Open API|https://spec.openapis.org/oas/v3.0.0> v3.0.0 for your reference. You can also find it L<here|https://gitlab.com/jackdeguest/Net-API-CPAN/-/blob/master/build/cpan-openapi-spec-3.0.0.json> in C<JSON> format.
4897              
4898             =head1 AUTHOR
4899              
4900             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
4901              
4902             =head1 SEE ALSO
4903              
4904             L<Meta CPAN API documentation|https://github.com/metacpan/metacpan-api/blob/master/docs/API-docs.md>
4905              
4906             L<https://metacpan.org/>, L<https://www.cpan.org/>
4907              
4908             L<Net::API::CPAN::Activity>, L<Net::API::CPAN::Author>, L<Net::API::CPAN::Changes>, L<Net::API::CPAN::Changes::Release>, L<Net::API::CPAN::Contributor>, L<Net::API::CPAN::Cover>, L<Net::API::CPAN::Diff>, L<Net::API::CPAN::Distribution>, L<Net::API::CPAN::DownloadUrl>, L<Net::API::CPAN::Favorite>, L<Net::API::CPAN::File>, L<Net::API::CPAN::Module>, L<Net::API::CPAN::Package>, L<Net::API::CPAN::Permission>, L<Net::API::CPAN::Rating>, L<Net::API::CPAN::Release>
4909              
4910             L<Net::API::CPAN::Filter>, L<Net::API::CPAN::List>, L<Net::API::CPAN::Scroll>
4911              
4912             L<Net::API::CPAN::Mock>
4913              
4914             =head1 COPYRIGHT & LICENSE
4915              
4916             Copyright(c) 2023 DEGUEST Pte. Ltd.
4917              
4918             All rights reserved
4919              
4920             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
4921              
4922             =cut