File Coverage

blib/lib/Bintray/API/Session.pm
Criterion Covered Total %
statement 30 107 28.0
branch 0 36 0.0
condition 0 12 0.0
subroutine 10 13 76.9
pod 0 3 0.0
total 40 171 23.3


line stmt bran cond sub pod time code
1             package Bintray::API::Session;
2              
3             #######################
4             # LOAD CORE MODULES
5             #######################
6 1     1   7 use strict;
  1         3  
  1         712  
7 1     1   8 use warnings FATAL => 'all';
  1         2  
  1         74  
8 1     1   7 use Carp qw(croak carp);
  1         1  
  1         126  
9              
10             #######################
11             # VERSION
12             #######################
13             our $VERSION = '1.0.2';
14              
15             #######################
16             # LOAD CPAN MODULES
17             #######################
18 1     1   801 use JSON::Any;
  1         19846  
  1         9  
19 1     1   6950 use Encode qw();
  1         12659  
  1         40  
20 1     1   959 use HTTP::Tiny qw();
  1         59445  
  1         55  
21 1     1   796 use URI::Encode qw();
  1         1760  
  1         41  
22 1     1   806 use MIME::Base64 qw(encode_base64);
  1         926  
  1         120  
23 1     1   11 use Params::Validate qw(validate_with :types);
  1         2  
  1         295  
24              
25 1         11 use Object::Tiny qw(
26             json
27             debug
28             apikey
29             apiurl
30             error
31             client
32             limits
33             hascreds
34             username
35             urlencoder
36 1     1   9 );
  1         2  
37              
38             #######################
39             # PUBLIC METHODS
40             #######################
41              
42             ## Constructor
43             sub new {
44 0     0 0   my ( $class, @args ) = @_;
45 0           my %opts = validate_with(
46             params => [@args],
47             spec => {
48             username => {
49             type => SCALAR,
50             default => '',
51             },
52             apikey => {
53             type => SCALAR,
54             default => '',
55             },
56             debug => {
57             type => BOOLEAN,
58             default => 0,
59             },
60             },
61             );
62              
63             # Set API URL
64 0           $opts{apiurl} = 'https://bintray.com/api/v1';
65              
66             # Check for credentials
67 0 0 0       if ( $opts{username} and $opts{apikey} ) {
68 0           $opts{hascreds} = 1;
69             }
70              
71             # Init HTTP Client
72 0 0         $opts{client} = HTTP::Tiny->new(
73             agent => 'perl-bintray-api-client',
74             default_headers => {
75             'Accept' => 'application/json',
76             'Content-Type' => 'application/json',
77              
78             # Save Credentials for Basic Auth
79             (
80             $opts{hascreds}
81             ? (
82             'Authorization' => sprintf(
83             '%s %s', 'Basic',
84             encode_base64(
85             join( ':', $opts{username}, $opts{apikey} ), ''
86             ),
87             ),
88             )
89             : ()
90             ),
91             },
92             );
93              
94             # Init Encoder
95 0           $opts{urlencoder} = URI::Encode->new();
96              
97             # Init JSON
98 0           $opts{json} = JSON::Any->new(
99             utf8 => 1,
100             );
101              
102             # Init Empty error
103 0           $opts{error} = '';
104              
105             # Return Object (tiny)
106 0           return $class->SUPER::new(%opts);
107             } ## end sub new
108              
109             ## Talk
110             sub talk {
111 0     0 0   my ( $self, @args ) = @_;
112 0           my %opts = validate_with(
113             params => [@args],
114             spec => {
115             method => {
116             type => SCALAR,
117             default => 'GET',
118             },
119             path => {
120             type => SCALAR,
121             },
122             query => {
123             type => ARRAYREF,
124             default => [],
125             },
126             params => {
127             type => ARRAYREF,
128             default => [],
129             },
130             content => {
131             type => SCALAR,
132             default => '',
133             },
134             wantheaders => {
135             type => BOOLEAN,
136             default => 0,
137             },
138             anon => {
139             type => BOOLEAN,
140             default => 0,
141             },
142             },
143             );
144              
145             # Check for Credentials
146 0 0         if ( not $opts{anon} ) {
147 0 0         croak "ERROR: API Method $opts{path} requires authentication."
148             . " Please set a username and apikey to use this."
149             unless $self->hascreds();
150             } ## end if ( not $opts{anon} )
151              
152             # Build Path
153 0           $opts{path} =~ s{^\/}{}x;
154 0           my $url = join( '/', $self->apiurl(), $opts{path} );
155              
156             # Build Query
157 0           my @query_parts;
158 0           foreach my $_q ( @{ $opts{query} } ) {
  0            
159 0           foreach my $_k ( keys %{$_q} ) {
  0            
160 0           push @query_parts, sprintf( '%s=%s', $_k, $_q->{$_k} );
161             }
162             } ## end foreach my $_q ( @{ $opts{query...}})
163 0 0         if (@query_parts) {
164 0           $url .= '?' . join( '&', @query_parts );
165             }
166              
167             # Build Params
168 0           my @param_parts;
169 0           foreach my $_p ( @{ $opts{params} } ) {
  0            
170 0           push @param_parts, sprintf( '%s=%s', each %{$_p} );
  0            
171             }
172 0 0         if (@param_parts) {
173 0           $url .= ';' . join( ';', @param_parts );
174             }
175              
176             # Encode
177 0           $url = $self->urlencoder->encode($url);
178              
179             # Talk
180 0 0         my $response = $self->client()->request(
181             uc( $opts{method} ), $url, # URL
182             {
183             # Check for content
184             $opts{content} ? ( content => $opts{content} ) : (),
185             }
186             );
187              
188             # Check Response
189 0 0         if ( not $response->{success} ) {
190 0 0         $self->{error}
191             = "API Call to $opts{path} failed : "
192             . " URL: $response->{url}."
193             . " STATUS: $response->{status}."
194             . " REASON: $response->{reason}."
195             . ( ( $response->{status} ne '404' )
196             ? " CONTENT: $response->{content}."
197             : '' );
198 0 0         carp $self->{error} if $self->debug;
199 0           return;
200             } ## end if ( not $response->{success...})
201              
202             # Collect Response
203 0           my $api_response_data;
204 0 0         if ( $response->{content} ) {
205 0           $api_response_data = $self->json->decode(
206             Encode::decode( 'utf-8-strict', $response->{content} ) );
207             } ## end if ( $response->{content...})
208              
209             # Collect Headers
210 0           my $api_headers = {};
211 0           foreach my $_h ( grep { /^x\-/xi } keys %{ $response->{headers} } ) {
  0            
  0            
212 0           $api_headers->{$_h} = $response->{headers}->{$_h};
213             }
214              
215             # Save Limits
216 0 0 0       if ( exists $api_headers->{'x-ratelimit-limit'}
217             and exists $api_headers->{'x-ratelimit-remaining'} )
218             {
219 0           $self->{limits} = {
220             limit => $api_headers->{'x-ratelimit-limit'},
221             remaining => $api_headers->{'x-ratelimit-remaining'},
222             };
223             } ## end if ( exists $api_headers...)
224              
225             # Return
226 0 0         if ( $opts{wantheaders} ) {
227             return {
228 0           headers => $api_headers,
229             data => $api_response_data,
230             };
231             } ## end if ( $opts{wantheaders...})
232 0           return $api_response_data;
233             } ## end sub talk
234              
235             ## Paginate
236             sub paginate {
237 0     0 0   my ( $self, @args ) = @_;
238 0           my %opts = validate_with(
239             params => [@args],
240             spec => {
241             query => {
242             type => ARRAYREF,
243             default => [],
244             },
245             max => {
246             type => SCALAR,
247             default => 200,
248             regex => qr/^\d+$/x,
249             },
250             },
251             allow_extra => 1,
252             );
253              
254 0           my $max_results = delete $opts{max};
255 0           my $num_of_results = 0;
256 0           my $start_pos = 0;
257 0           my $data = [];
258 0           while (1) {
259              
260             # Talk
261 0           my $response = $self->talk(
262             %opts,
263             wantheaders => 1,
264 0           query => [ { start_pos => $start_pos }, @{ $opts{query} }, ],
265             );
266 0 0         last if not defined $response;
267              
268             # Check data
269 0 0         if ( ref( $response->{data} ) eq 'ARRAY' ) {
270 0           push @$data, @{ $response->{data} };
  0            
271 0           $num_of_results += scalar( @{ $response->{data} } );
  0            
272             } ## end if ( ref( $response->{...}))
273             else {
274 0           $data = $response->{data};
275 0           last;
276             } ## end else [ if ( ref( $response->{...}))]
277              
278             # Get position
279 0   0       my $_total = $response->{headers}->{'x-rangelimit-total'} || 0;
280 0   0       my $_start = $response->{headers}->{'x-rangelimit-startpos'} || 0;
281 0   0       my $_end = $response->{headers}->{'x-rangelimit-endpos'} || 0;
282 0           my $_per_page = $_end - $_start;
283              
284             # Update Current
285 0           $start_pos = $_end + 1;
286              
287             # Continue paging?
288 0 0         last if ( $num_of_results >= $max_results );
289 0 0         last if ( $num_of_results >= $_total );
290             } ## end while (1)
291              
292             # Return
293 0 0         if ( $opts{wantheaders} ) {
294 0           return { data => $data };
295             }
296 0           return $data;
297             } ## end sub paginate
298              
299             #######################
300             1;
301              
302             __END__