File Coverage

blib/lib/MetaCPAN/Client/Request.pm
Criterion Covered Total %
statement 119 129 92.2
branch 36 60 60.0
condition 10 20 50.0
subroutine 20 20 100.0
pod 3 3 100.0
total 188 232 81.0


line stmt bran cond sub pod time code
1 21     21   125270 use strict;
  21         63  
  21         948  
2 21     21   134 use warnings;
  21         39  
  21         1700  
3             package MetaCPAN::Client::Request;
4             # ABSTRACT: Object used for making requests to MetaCPAN
5             $MetaCPAN::Client::Request::VERSION = '2.040000';
6 21     21   550 use Moo;
  21         8108  
  21         192  
7 21     21   10610 use Carp;
  21         67  
  21         2066  
8 21     21   171 use JSON::MaybeXS qw;
  21         50  
  21         1629  
9 21     21   518 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  21         1845  
  21         1326  
10              
11 21     21   11166 use MetaCPAN::Client::Scroll;
  21         98  
  21         1071  
12 21     21   217 use MetaCPAN::Client::Types qw< HashRef Int >;
  21         39  
  21         47414  
13              
14             with 'MetaCPAN::Client::Role::HasUA';
15              
16             has _clientinfo => (
17             is => 'ro',
18             isa => HashRef,
19             lazy => 1,
20             builder => '_build_clientinfo',
21             );
22              
23             has domain => (
24             is => 'ro',
25             default => sub {
26             $ENV{METACPAN_DOMAIN} and return $ENV{METACPAN_DOMAIN};
27             $_[0]->_clientinfo->{production}{domain};
28             },
29             );
30              
31             has base_url => (
32             is => 'ro',
33             lazy => 1,
34             default => sub {
35             $ENV{METACPAN_DOMAIN} and return $ENV{METACPAN_DOMAIN};
36             $_[0]->_clientinfo->{production}{url};
37             },
38             );
39              
40             has _is_agg => (
41             is => 'ro',
42             default => 0,
43             writer => '_set_is_agg'
44             );
45              
46             has debug => (
47             is => 'ro',
48             isa => Int,
49             default => 0,
50             );
51              
52             sub BUILDARGS {
53 22     22 1 385716 my ( $self, %args ) = @_;
54 22 100       118 $args{domain} and $args{base_url} = $args{domain};
55 22         497 return \%args;
56             }
57              
58             sub _build_clientinfo {
59 20     20   283 my $self = shift;
60              
61 20         45 my $info;
62 20 100       42 eval {
63 20         538 $info = $self->ua->get( 'https://clientinfo.metacpan.org' );
64 1         926266 $info = decode_json( $info->{content} );
65 1 50 33     131 is_hashref($info) and exists $info->{production} or die;
66 1         4 1;
67             }
68             or $info = +{
69             production => {
70             url => 'https://fastapi.metacpan.org', # last known production url
71             domain => 'https://fastapi.metacpan.org', # last known production domain
72             }
73             };
74              
75 20         453 return $info;
76             }
77              
78             sub fetch {
79 47     47 1 2041 my $self = shift;
80 47 50       204 my $url = shift or croak 'fetch must be called with a URL parameter';
81 47   100     263 my $params = shift || {};
82 47         2307 $url =~ s{^/}{};
83 47         2885 my $req_url = sprintf '%s/%s', $self->base_url, $url;
84 47         1732 my $ua = $self->ua;
85              
86 47 100       2323 my $result = keys %{$params}
  47         1294  
87             ? $ua->post( $req_url, { content => encode_json $params } )
88             : $ua->get($req_url);
89              
90 47         8958621 return $self->_decode_result( $result, $req_url );
91             }
92              
93             sub ssearch {
94 10     10 1 434 my $self = shift;
95 10         24 my $type = shift;
96 10         21 my $args = shift;
97 10         22 my $params = shift;
98              
99 10   50     61 my $time = delete $params->{'scroller_time'} || '5m';
100 10   50     58 my $size = delete $params->{'scroller_size'} || 1000;
101              
102 10         221 my $scroller = MetaCPAN::Client::Scroll->new(
103             ua => $self->ua,
104             size => $size,
105             time => $time,
106             base_url => $self->base_url,
107             type => $type,
108             body => $self->_build_body($args, $params),
109             debug => $self->debug,
110             );
111              
112 10         1325 return $scroller;
113             }
114              
115             sub _decode_result {
116 47     47   207 my $self = shift;
117 47         114 my $result = shift;
118 47 50       242 my $url = shift or croak 'Second argument of a URL must be provided';
119              
120 47 50       248 is_hashref($result)
121             or croak 'First argument must be hashref';
122              
123 47         144 my $success = $result->{'success'};
124              
125 47 50       167 defined $success
126             or croak 'Missing success in return value';
127              
128 47 50       194 if (!$success) {
129 0 0       0 my $reason_field = $result->{status} == 599 ? 'content' : 'reason';
130 0         0 croak "Failed to fetch '$url': " . $result->{$reason_field};
131             }
132              
133 47 50       631 my $content = $result->{'content'}
134             or croak 'Missing content in return value';
135              
136 47 100       309 $url =~ m|/pod/| and return $content;
137 46 100       270 $url =~ m|/source/| and return $content;
138              
139 45         110 my $decoded_result;
140             eval {
141 45         9633 $decoded_result = decode_json $content;
142 45         228 1;
143 45 50       104 } or do {
144 0         0 croak "Couldn't decode '$content': $@";
145             };
146              
147 45         724 return $decoded_result;
148             }
149              
150             sub _build_body {
151 10     10   342 my $self = shift;
152 10         26 my $args = shift;
153 10         20 my $params = shift;
154              
155             my $query = $args->{__MATCH_ALL__}
156 10 50       65 ? { match_all => {} }
157             : _build_query_rec($args);
158              
159 10         50 $query = $self->_apply_filters($query, $params);
160              
161             return +{
162 10         53 query => $query,
163             $self->_read_fields($params),
164             $self->_read_aggregations($params),
165             $self->_read_sort($params)
166             };
167             }
168              
169             my %key2es = (
170             all => 'must',
171             either => 'should',
172             not => 'must_not',
173             );
174              
175             sub _read_fields {
176 10     10   40 my $self = shift;
177 10         19 my $params = shift;
178              
179 10         24 my $fields = delete $params->{fields};
180 10         24 my $_source = delete $params->{_source};
181              
182 10         19 my @ret;
183              
184 10 50       32 if ( $fields ) {
185 0 0       0 is_arrayref($fields) or
186             croak "fields must be an arrayref";
187 0         0 push @ret => ( fields => $fields );
188             }
189              
190 10 50       31 if ( $_source ) {
191 0 0 0     0 is_arrayref($_source) or !is_ref($_source) or
192             croak "_source must be an arrayref or a string";
193 0         0 push @ret => ( _source => $_source );
194             }
195              
196 10         41 return @ret;
197             }
198              
199             sub _read_aggregations {
200 10     10   20 my $self = shift;
201 10         21 my $params = shift;
202              
203 10         20 my $aggregations = delete $params->{aggregations};
204 10 50       72 is_ref($aggregations) or return ();
205              
206 0         0 $self->_set_is_agg(1);
207 0         0 return ( aggregations => $aggregations );
208             }
209              
210             sub _apply_filters {
211 10     10   21 my $self = shift;
212 10         24 my $query = shift;
213 10         22 my $params = shift;
214              
215 10         25 my $filter = delete $params->{es_filter};
216 10 100       43 is_ref($filter) or return $query;
217              
218 1         7 return { bool => { must => [ $filter, $query ] } };
219             }
220              
221             sub _read_sort {
222 10     10   23 my $self = shift;
223 10         19 my $params = shift;
224              
225 10         22 my $sort = delete $params->{sort};
226 10 50       290 is_ref($sort) or return ();
227              
228 0         0 return ( sort => $sort );
229             }
230              
231             sub _build_query_rec {
232 28     28   66 my $args = shift;
233 28 50       92 is_hashref($args) or croak 'query args must be a hash';
234              
235 28         56 my %query = ();
236 28         46 my $basic_element = 1;
237              
238 28         60 KEY: for my $k ( qw/ all either not / ) {
239 84   100     1313 my $v = delete $args->{$k} || next KEY;
240 9 50       24 is_hashref($v) and $v = [ $v ];
241 9 50       38 is_arrayref($v) or croak "invalid value for key $k";
242              
243 9         96 undef $basic_element;
244              
245 9         36 $query{'bool'}{ $key2es{$k} } =
246             [ map +( _build_query_rec($_) ), @$v ];
247              
248 9 100       37 $k eq 'either' and $query{'bool'}{'minimum_should_match'} = 1;
249             }
250              
251 28 100       78 $basic_element and %query = %{ _build_query_element($args) };
  20         56  
252              
253 28         126 return \%query;
254             }
255              
256             sub _build_query_element {
257 24     24   497301 my $args = shift;
258              
259 24 50       167 scalar keys %{$args} == 1
  24         90  
260             or croak 'Wrong number of keys in query element';
261              
262 24         82 my ($key) = keys %{$args};
  24         87  
263 24         62 my $val = $args->{$key};
264              
265 24 50 33     121 is_bool($val) or !is_ref($val) and $val =~ /[\w\*]/
      66        
266             or croak 'Wrong type of query arguments';
267              
268 24         425 my $wildcard = $val =~ /[*?]/;
269 24 100       169 my $qtype = $wildcard ? 'wildcard' : 'term';
270              
271 24         128 return +{ $qtype => $args };
272             }
273              
274              
275             1;
276              
277             __END__