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   109513 use strict;
  21         32  
  21         663  
2 21     21   74 use warnings;
  21         30  
  21         1182  
3             package MetaCPAN::Client::Request;
4             # ABSTRACT: Object used for making requests to MetaCPAN
5             $MetaCPAN::Client::Request::VERSION = '2.043000';
6 21     21   476 use Moo;
  21         7500  
  21         104  
7 21     21   7244 use Carp;
  21         38  
  21         1479  
8 21     21   112 use JSON::MaybeXS qw;
  21         31  
  21         1080  
9 21     21   548 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  21         1818  
  21         856  
10              
11 21     21   7639 use MetaCPAN::Client::Scroll;
  21         77  
  21         865  
12 21     21   151 use MetaCPAN::Client::Types qw< HashRef Int >;
  21         69  
  21         33473  
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 296127 my ( $self, %args ) = @_;
54 22 100       155 $args{domain} and $args{base_url} = $args{domain};
55 22         442 return \%args;
56             }
57              
58             sub _build_clientinfo {
59 20     20   222 my $self = shift;
60              
61 20         41 my $info;
62 20 100       40 eval {
63 20         340 $info = $self->ua->get( 'https://clientinfo.metacpan.org' );
64 1         440495 $info = decode_json( $info->{content} );
65 1 50 33     83 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         328 return $info;
76             }
77              
78             sub fetch {
79 49     49 1 1591 my $self = shift;
80 49 50       228 my $url = shift or croak 'fetch must be called with a URL parameter';
81 49   100     288 my $params = shift || {};
82 49         135 $url =~ s{^/}{};
83 49         1017 my $req_url = sprintf '%s/%s', $self->base_url, $url;
84 49         1399 my $ua = $self->ua;
85              
86 49 100       1653 my $result = keys %{$params}
  49         990  
87             ? $ua->post( $req_url, { content => encode_json $params } )
88             : $ua->get($req_url);
89              
90 49         7611568 return $self->_decode_result( $result, $req_url );
91             }
92              
93             sub ssearch {
94 10     10 1 394 my $self = shift;
95 10         25 my $index = shift;
96 10         20 my $args = shift;
97 10         19 my $params = shift;
98              
99 10   50     71 my $time = delete $params->{'scroller_time'} || '5m';
100 10   50     45 my $size = delete $params->{'scroller_size'} || 1000;
101              
102 10         219 my $scroller = MetaCPAN::Client::Scroll->new(
103             ua => $self->ua,
104             size => $size,
105             time => $time,
106             base_url => $self->base_url,
107             index => $index,
108             body => $self->_build_body($args, $params),
109             debug => $self->debug,
110             );
111              
112 10         683 return $scroller;
113             }
114              
115             sub _decode_result {
116 49     49   163 my $self = shift;
117 49         122 my $result = shift;
118 49 50       231 my $url = shift or croak 'Second argument of a URL must be provided';
119              
120 49 50       230 is_hashref($result)
121             or croak 'First argument must be hashref';
122              
123 49         126 my $success = $result->{'success'};
124              
125 49 50       160 defined $success
126             or croak 'Missing success in return value';
127              
128 49 50       159 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 49 50       512 my $content = $result->{'content'}
134             or croak 'Missing content in return value';
135              
136 49 100       356 $url =~ m|/pod/| and return $content;
137 48 100       245 $url =~ m|/source/| and return $content;
138              
139 47         99 my $decoded_result;
140             eval {
141 47         6162 $decoded_result = decode_json $content;
142 47         211 1;
143 47 50       89 } or do {
144 0         0 croak "Couldn't decode '$content': $@";
145             };
146              
147 47         663 return $decoded_result;
148             }
149              
150             sub _build_body {
151 10     10   304 my $self = shift;
152 10         19 my $args = shift;
153 10         22 my $params = shift;
154              
155             my $query = $args->{__MATCH_ALL__}
156 10 50       70 ? { match_all => {} }
157             : _build_query_rec($args);
158              
159 10         38 $query = $self->_apply_filters($query, $params);
160              
161             return +{
162 10         91 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   71 my $self = shift;
177 10         24 my $params = shift;
178              
179 10         21 my $fields = delete $params->{fields};
180 10         94 my $_source = delete $params->{_source};
181              
182 10         57 my @ret;
183              
184 10 50       30 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       25 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         37 return @ret;
197             }
198              
199             sub _read_aggregations {
200 10     10   16 my $self = shift;
201 10         21 my $params = shift;
202              
203 10         20 my $aggregations = delete $params->{aggregations};
204 10 50       150 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   18 my $self = shift;
212 10         17 my $query = shift;
213 10         18 my $params = shift;
214              
215 10         22 my $filter = delete $params->{es_filter};
216 10 100       34 is_ref($filter) or return $query;
217              
218 1         4 return { bool => { must => [ $filter, $query ] } };
219             }
220              
221             sub _read_sort {
222 10     10   18 my $self = shift;
223 10         23 my $params = shift;
224              
225 10         16 my $sort = delete $params->{sort};
226 10 50       268 is_ref($sort) or return ();
227              
228 0         0 return ( sort => $sort );
229             }
230              
231             sub _build_query_rec {
232 28     28   43 my $args = shift;
233 28 50       61 is_hashref($args) or croak 'query args must be a hash';
234              
235 28         56 my %query = ();
236 28         37 my $basic_element = 1;
237              
238 28         55 KEY: for my $k ( qw/ all either not / ) {
239 84   100     178 my $v = delete $args->{$k} || next KEY;
240 9 50       21 is_hashref($v) and $v = [ $v ];
241 9 50       20 is_arrayref($v) or croak "invalid value for key $k";
242              
243 9         14 undef $basic_element;
244              
245 9         29 $query{'bool'}{ $key2es{$k} } =
246             [ map +( _build_query_rec($_) ), @$v ];
247              
248 9 100       28 $k eq 'either' and $query{'bool'}{'minimum_should_match'} = 1;
249             }
250              
251 28 100       59 $basic_element and %query = %{ _build_query_element($args) };
  20         47  
252              
253 28         107 return \%query;
254             }
255              
256             sub _build_query_element {
257 24     24   250824 my $args = shift;
258              
259 24 50       34 scalar keys %{$args} == 1
  24         72  
260             or croak 'Wrong number of keys in query element';
261              
262 24         37 my ($key) = keys %{$args};
  24         54  
263 24         50 my $val = $args->{$key};
264              
265 24 50 33     88 ( is_bool($val) or (!is_ref($val) and $val =~ /[\w\*]/) )
      66        
266             or croak 'Wrong type of query arguments';
267              
268 24         316 my $wildcard = $val =~ /[*?]/;
269 24 100       64 my $qtype = $wildcard ? 'wildcard' : 'term';
270              
271 24         86 return +{ $qtype => $args };
272             }
273              
274              
275             1;
276              
277             __END__