File Coverage

blib/lib/MetaCPAN/API.pm
Criterion Covered Total %
statement 74 74 100.0
branch 21 22 95.4
condition 6 8 75.0
subroutine 17 17 100.0
pod 2 2 100.0
total 120 123 97.5


line stmt bran cond sub pod time code
1 15     15   518775 use strict;
  15         35  
  15         552  
2 15     15   74 use warnings;
  15         20  
  15         684  
3             package MetaCPAN::API;
4             # ABSTRACT: A comprehensive, DWIM-featured API to MetaCPAN (DEPRECATED)
5             $MetaCPAN::API::VERSION = '0.50';
6 15     15   9342 use Moo;
  15         218132  
  15         97  
7 15     15   33625 use Types::Standard qw;
  15         1139725  
  15         297  
8 15     15   26925 use namespace::autoclean;
  15         201214  
  15         109  
9              
10 15     15   1089 use Carp;
  15         30  
  15         1333  
11 15     15   13239 use JSON;
  15         192464  
  15         106  
12 15     15   2480 use Try::Tiny;
  15         30  
  15         1325  
13 15     15   12678 use HTTP::Tiny;
  15         716872  
  15         628  
14 15     15   9232 use URI::Escape 'uri_escape';
  15         17760  
  15         12274  
15              
16             with qw/
17             MetaCPAN::API::Author
18             MetaCPAN::API::Distribution
19             MetaCPAN::API::Favorite
20             MetaCPAN::API::File
21             MetaCPAN::API::Autocomplete
22             MetaCPAN::API::Module
23             MetaCPAN::API::POD
24             MetaCPAN::API::Rating
25             MetaCPAN::API::Release
26             MetaCPAN::API::Source
27             /;
28              
29             has base_url => (
30             is => 'ro',
31             isa => Str,
32             default => sub{'http://api.metacpan.org/v0'},
33             );
34              
35             has ua => (
36             is => 'ro',
37             lazy => 1,
38             builder => '_build_ua',
39             isa => InstanceOf['HTTP::Tiny'],
40             );
41              
42             has ua_args => (
43             is => 'ro',
44             isa => ArrayRef,
45             default => sub {
46             my $version = $MetaCPAN::API::VERSION || 'xx';
47             return [ agent => "MetaCPAN::API/$version" ];
48             },
49             );
50              
51             sub _build_ua {
52 13     13   8814 my $self = shift;
53 13         145 return HTTP::Tiny->new( @{ $self->ua_args } );
  13         149  
54             }
55              
56             sub fetch {
57 15     15 1 1666 my $self = shift;
58 15         36 my $url = shift;
59 15         78 my $extra = $self->_build_extra_params(@_);
60 15         93 my $base = $self->base_url;
61 15 100       87 my $req_url = $extra ? "$base/$url?$extra" : "$base/$url";
62              
63 15         366 my $result = $self->ua->get($req_url);
64 15         539539 return $self->_decode_result( $result, $req_url );
65             }
66              
67             sub post {
68 5     5 1 3212 my $self = shift;
69 5         9 my $url = shift;
70 5         6 my $query = shift;
71 5         16 my $base = $self->base_url;
72              
73 5 100       208 defined $url
74             or croak 'First argument of URL must be provided';
75              
76 4 100 66     241 ref $query and ref $query eq 'HASH'
77             or croak 'Second argument of query hashref must be provided';
78              
79 2         14 my $query_json = to_json( $query, { canonical => 1 } );
80 2         136 my $result = $self->ua->request(
81             'POST',
82             "$base/$url",
83             {
84             headers => { 'Content-Type' => 'application/json' },
85             content => $query_json,
86             }
87             );
88              
89 2         3513 return $self->_decode_result( $result, $url, $query_json );
90             }
91              
92             sub _decode_result {
93 24     24   3617 my $self = shift;
94 24         60 my ( $result, $url, $original ) = @_;
95 24         44 my $decoded_result;
96              
97 24 100 66     431 ref $result and ref $result eq 'HASH'
98             or croak 'First argument must be hashref';
99              
100 23 100       165 defined $url
101             or croak 'Second argument of a URL must be provided';
102              
103 22 100       104 if ( defined ( my $success = $result->{'success'} ) ) {
104 21   100     102 my $reason = $result->{'reason'} || '';
105 21 100       84 $reason .= ( defined $original ? " (request: $original)" : '' );
106              
107 21 100       399 $success or croak "Failed to fetch '$url': $reason";
108             } else {
109 1         117 croak 'Missing success in return value';
110             }
111              
112 19 50       302 defined ( my $content = $result->{'content'} )
113             or croak 'Missing content in return value';
114              
115 19     19   4458 try { $decoded_result = decode_json $content }
116 19     1   279 catch { croak "Couldn't decode '$content': $_" };
  1         191  
117              
118 18         576 return $decoded_result;
119             }
120              
121             sub _build_extra_params {
122 19     19   2390 my $self = shift;
123              
124 19 100       317 @_ % 2 == 0
125             or croak 'Incorrect number of params, must be key/value';
126 18         59 my %extra = @_;
127              
128             # if it's deep, JSON encoding needs to be involved
129 18 100       79 if (scalar grep { ref } values %extra) {
  13         36  
130 1         10 my $query_json = to_json( \%extra, { canonical => 1 } );
131 1         91 %extra = ( source => $query_json );
132             }
133              
134 11         171 my $extra = join '&', map {
135 18         94 "$_=" . uri_escape( $extra{$_} )
136             } sort keys %extra;
137              
138 18         529 return $extra;
139             }
140              
141             1;
142              
143             __END__