File Coverage

lib/Net/API/CPAN.pm
Criterion Covered Total %
statement 59 678 8.7
branch 8 590 1.3
condition 3 495 0.6
subroutine 18 77 23.3
pod 37 37 100.0
total 125 1877 6.6


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