Tie-Google-Sheets
view release on metacpan or search on metacpan
lib/Tie/Google/Sheets/Client.pm view on Meta::CPAN
my $ua_arg = delete $args{ua};
my $any_ua_arg = delete $args{any_ua};
croak 'only one of ua or any_ua may be given' if defined $ua_arg && defined $any_ua_arg;
my $batch_size = delete $args{batch_size};
croak 'batch_size must be a positive integer'
if defined $batch_size && $batch_size !~ /\A[1-9][0-9]*\z/;
my $backoff_retry = delete $args{backoff_retry};
croak 'backoff_retry must be a positive integer'
if defined $backoff_retry && $backoff_retry !~ /\A[1-9][0-9]*\z/;
croak "unknown constructor argument(s): @{[ sort keys %args ]}" if %args;
my $ua = $any_ua_arg // HTTP::AnyUA->new( ua => $ua_arg // do {
require HTTP::Tiny;
HTTP::Tiny->new
});
return {
spreadsheet_id => $spreadsheet_id,
ua => $ua,
access_token => $access_token,
service_account => defined $service_account
? $class->_load_service_account($service_account)
: undef,
batch_size => $batch_size,
backoff_retry => $backoff_retry,
};
}
sub _load_service_account ($class, $val) {
return $val if is_plain_hashref($val);
croak 'service_account must be a hashref of the decoded key file, or a path to one'
unless !is_ref($val) && path($val)->is_file;
my $json;
try { $json = path($val)->slurp_raw }
catch ($e) { croak "unable to open service account key file $val: $!" }
return decode_json($json);
}
sub _access_token ($self) {
if(defined(my $token = $self->access_token)) {
return is_plain_coderef($token) ? $token->() : $token;
}
return $self->token if $self->token && time() < $self->token_expires - 30;
my $sa = $self->service_account;
my $now = time();
my $jwt = encode_jwt(
payload => {
iss => $sa->{client_email},
scope => SCOPE,
aud => $sa->{token_uri} // TOKEN_URI,
iat => $now,
exp => $now + 3600,
},
key => \$sa->{private_key},
alg => 'RS256',
);
my $body = join '&',
'grant_type=' . uri_escape('urn:ietf:params:oauth:grant-type:jwt-bearer'),
'assertion=' . uri_escape($jwt);
my $res = $self->ua->request( 'POST', $sa->{token_uri} // TOKEN_URI, {
headers => { 'content-type' => 'application/x-www-form-urlencoded' },
content => $body,
});
$self->_croak_on_error($res);
my $data = decode_json($res->{content});
$self->token($data->{access_token});
$self->token_expires($now + ($data->{expires_in} // 3600));
return $self->token;
}
sub _croak_on_error ($self, $res) {
return if $res->{success};
my $message = $res->{reason} // 'unknown error';
if($res->{content}) {
my $data;
try {
$data = decode_json $res->{content};
$message = $data->{error}{message} if is_plain_hashref($data) && $data->{error}{message};
} catch ($e) {
warn "warning decoding JSON: $e";
}
}
croak "Google Sheets API error ($res->{status}): $message";
}
sub _request ($self, $method, $url, %opts) {
my %headers = ( authorization => 'Bearer ' . $self->_access_token, ( $opts{headers} // {} )->%* );
my %http_opts = ( headers => \%headers );
if(defined $opts{body}) {
$headers{'content-type'} = 'application/json';
$http_opts{content} = encode_json($opts{body});
}
my $attempts = ($self->backoff_retry // 0) + 1;
my $res;
for my $attempt (1 .. $attempts) {
$res = $self->ua->request($method, $url, \%http_opts);
last if $res->{success} || $res->{status} != 429 || $attempt == $attempts;
$SLEEP->(2 ** ($attempt - 1));
}
$self->_croak_on_error($res);
return undef unless length $res->{content};
return decode_json($res->{content});
}
sub _url ($self, $segments = [], $action = undef, %query) {
my $uri = URI->new(API_BASE);
$uri->path_segments($uri->path_segments, $self->spreadsheet_id, $segments->@*);
( run in 2.843 seconds using v1.01-cache-2.11-cpan-751830e7986 )