File Coverage

blib/lib/WebService/GoogleAPI/Client/UserAgent.pm
Criterion Covered Total %
statement 57 77 74.0
branch 18 50 36.0
condition 7 17 41.1
subroutine 12 12 100.0
pod 3 4 75.0
total 97 160 60.6


line stmt bran cond sub pod time code
1 3     3   32 use strictures;
  3         8  
  3         28  
2              
3             package WebService::GoogleAPI::Client::UserAgent;
4              
5             our $VERSION = '0.27'; # VERSION
6             # ABSTRACT: User Agent wrapper for working with Google APIs
7              
8 3     3   616 use Moo;
  3         6  
  3         27  
9              
10             extends 'Mojo::UserAgent';
11              
12             #extends 'Mojo::UserAgent::Mockable';
13 3     3   1034 use WebService::GoogleAPI::Client::AuthStorage::GapiJSON;
  3         6  
  3         91  
14 3     3   1691 use Mojo::UserAgent;
  3         709545  
  3         28  
15 3     3   175 use Data::Dump qw/pp/; # for dev debug
  3         8  
  3         182  
16 3     3   1882 use Data::Printer filters => 'Web';
  3         96477  
  3         34  
17              
18 3     3   1977 use Carp qw/croak carp cluck/;
  3         12  
  3         3386  
19              
20             has 'do_autorefresh' => (is => 'rw', default => 1);
21             has 'debug' => (is => 'rw', default => 0);
22             has 'auth_storage' => (
23             is => 'rw',
24             default => sub {
25             WebService::GoogleAPI::Client::AuthStorage::GapiJSON->new;
26             },
27             handles => [qw/get_access_token scopes user/],
28             trigger => 1,
29             isa => sub {
30             my $role = 'WebService::GoogleAPI::Client::AuthStorage';
31             die "auth_storage must implement the $role role to work!"
32             unless $_[0]->does($role);
33             },
34             lazy => 1
35             );
36              
37             sub _trigger_auth_storage {
38 3     3   398 my ($self) = @_;
39              
40             # give the auth_storage a ua
41             # TODO - this seems like code smell to me. Should these storage things be
42             # roles that get applied to this ua?
43 3         66 $self->auth_storage->ua($self);
44             }
45              
46             ## NB - could cache using https://metacpan.org/pod/Mojo::UserAgent::Cached
47             # TODO: Review source of this for ideas
48              
49              
50             ## NB - used by both Client and Discovery
51              
52             # Keep access_token in headers always actual
53              
54             ## performance tip as per https://developers.google.com/calendar/performance and similar links
55             ## NB - to work with Google APIs also assumes that Accept-Encoding: gzip is set in HTTP headers
56             sub BUILD {
57 4     4 0 5731 my ($self) = @_;
58 4         34 $self->transactor->name(__PACKAGE__ . ' (gzip enabled)');
59             ## MAX SIZE ETC _ WHAT OTHER CONFIGURABLE PARAMS ARE AVAILABLE
60             }
61              
62              
63              
64             sub header_with_bearer_auth_token {
65 1     1 1 3 my ($self, $headers) = @_;
66              
67 1 50       5 $headers = {} unless defined $headers;
68              
69 1         3 $headers->{'Accept-Encoding'} = 'gzip';
70              
71 1 50       25 if (my $token = $self->get_access_token) {
72 1         5 $headers->{Authorization} = "Bearer $token";
73             } else {
74              
75             # TODO - why is this not fatal?
76 0         0 carp "Can't build Auth header, couldn't get an access token. Is your AuthStorage set up correctly?";
77             }
78 1         10 return $headers;
79             }
80              
81              
82             sub build_http_transaction {
83 1     1 1 5 my ($self, $params) = @_;
84             ## hack to allow method option as alias for httpMethod
85              
86 1 50       6 $params->{httpMethod} = $params->{method} if defined $params->{method};
87 1 50       6 $params->{httpMethod} = '' unless defined $params->{httpMethod};
88              
89 1   50     6 my $http_method = uc($params->{httpMethod}) || 'GET'; # uppercase ?
90 1   50     5 my $optional_data = $params->{options} || '';
91             my $path = $params->{path}
92 1   33     4 || cluck('path parameter required for build_http_transaction');
93             my $no_auth = $params->{no_auth}
94 1   50     6 || 0; ## default to including auth header - ie not setting no_auth
95 1   50     7 my $headers = $params->{headers} || {};
96              
97 1 50 33     6 cluck 'Attention! You are using POST, but no payload specified'
98             if (($http_method eq 'POST') && !defined $optional_data);
99 1 50       6 cluck "build_http_transaction:: $http_method $path " if ($self->debug > 11);
100 1 50       7 cluck "$http_method Not a SUPPORTED HTTP method parameter specified to build_http_transaction" . pp $params
101             unless $http_method =~ /^GET|PATH|PUT|POST|PATCH|DELETE$/ixm;
102              
103             ## NB - headers not passed if no_auth
104 1 50       8 $headers = $self->header_with_bearer_auth_token($headers) unless $no_auth;
105 1 50       8 if ($http_method =~ /^POST|PATH|PUT|PATCH$/ixg) {
106             ## ternary conditional on whether optional_data is set
107             ## return $optional_data eq '' ? $self->build_tx( $http_method => $path => $headers ) : $self->build_tx( $http_method => $path => $headers => json => $optional_data );
108 0 0       0 if ($optional_data eq '') {
109 0         0 return $self->build_tx($http_method => $path => $headers);
110             } else {
111 0 0       0 if (ref($optional_data) eq 'HASH') {
    0          
112 0         0 return $self->build_tx($http_method => $path => $headers => json => $optional_data);
113             } elsif (ref($optional_data) eq '') ## am assuming is a post with options containing a binary payload
114             {
115 0         0 return $self->build_tx($http_method => $path => $headers => $optional_data);
116             }
117              
118             }
119             } else { ## DELETE or GET
120 1 50       16 return $self->build_tx($http_method => $path => $headers => form => $optional_data)
121             if ($http_method eq 'GET');
122 0 0       0 return $self->build_tx($http_method => $path => $headers)
123             if ($http_method eq 'DELETE');
124             }
125              
126             #return undef; ## assert: should never get here
127             }
128              
129              
130              
131              
132             # NOTE validated means that we assume checking against discovery specs has already been done.
133             sub validated_api_query {
134 1     1 1 4 my ($self, $params) = @_;
135              
136             ## assume is a GET for the URI at $params
137 1 50       11 if (ref($params) eq '') {
138 1 50       6 cluck("transcribing $params to a hashref for validated_api_query")
139             if $self->debug;
140 1         3 my $val = $params;
141 1         7 $params = { path => $val, method => 'get', options => {}, };
142             }
143              
144 1         6 my $tx = $self->build_http_transaction($params);
145              
146 1 50       548 cluck("$params->{method} $params->{path}") if $self->debug;
147              
148             #TODO- figure out how we can alter this to use promises
149             # at this point, i think we'd have to make a different method entirely to
150             # do this promise-wise
151 1         8 my $res = $self->start($tx)->res;
152 1         178030 $res->{_token} = $self->get_access_token;
153 1 50       43 if (!$res->code) {
154 0         0 cluck "Response has no code! Network error? here's the object:\n" . np $res;
155 0         0 return $res;
156             }
157              
158 1 50 33     13 if (($res->code == 401) && $self->do_autorefresh) {
    50          
    50          
159 0 0       0 cluck "Your access token was expired. Attempting to update it automatically..."
160             if ($self->debug > 11);
161              
162 0         0 $self->auth_storage->refresh_access_token($self);
163              
164 0         0 return $self->validated_api_query($params);
165             } elsif ($res->code == 403) {
166 0         0 cluck('Unexpected permission denied 403 error');
167 0         0 return $res;
168             } elsif ($res->code == 429) {
169 0         0 cluck('HTTP 429 - you hit a rate limit. Try again later');
170 0         0 return $res;
171             }
172 1 50       27 return $res if $res->code == 200;
173 0 0         return $res if $res->code == 204; ## NO CONTENT - INDICATES OK FOR DELETE ETC
174 0 0         return $res if $res->code == 400; ## general failure
175 0           cluck("unhandled validated_api_query response code; here's the object:\n" . np $res);
176 0           return $res;
177             }
178              
179             1;
180              
181             __END__