Apologies for the long question. I am working on the raku Net::Google::Sheets module.
Currently it does a good job of successfully using OAuth2::Client::Google and getting and putting a 2D Array from/to a Google Sheet.
I want to add a $sheet.clear
method in line with the Google Sheets API.
Here is the relevant class in /lib/Net/Google/Sheets.rakumod:
class Sheet is export {
has $.session;
has $.id;
has $.range;
method url {
"$sheet-base/{$!id}/values/{$!range}"
}
method clear {
my $request = HTTP::Request.new(
POST => ($.url ~ ':clear'),
Authorization => "Bearer {$.session.token}",
Content-length => 0,
);
$ua.request($request).decoded-content.&from-json;
}
}
And here is the script that calls it:
#!/usr/bin/env raku
use Net::Google::Sheets;
my $session = Session.new;
my %sheets = $session.sheets;
my $id = %sheets<AWS_EC2_Sizes_test>;
$sheet2.clear; # <=== gives error
And here is the output and error that I am getting...
The requested URL <code>/v4/spreadsheets/{SheetId}/values/Sheet2%3Aclear</code> was not found on this server. <ins>That’s all we know.</ins>
The Oauth access is working fine for read and write and the sheet ID is correct. I have also got a successful .clear action via the Google reference doc "Try It" box.
I suspect that there is some subtlety around gRPC that I am not following (maybe I need to encode the ':' colon character another way?).
Please can you help?
Thanks to @raiph answer, see below, the problem was that handing URI a string for the entire url means that it is all url-encoded and (as raiph points out) Google wants the colon :
as is. The following code works fine:
method uri( :$cmd ) {
my $uri = URI.new: $sheet-url;
my $path = "$sheet-path/{$!id}/values/{$!range}";
$path ~= ":$cmd" if $cmd;
$uri.path: $path;
$uri;
}
method clear {
my $cmd = 'clear';
my $request = HTTP::Request.new(
POST => $.uri( :$cmd ),
Authorization => "Bearer {$.session.token}",
Content-length => 0,
);
$ua.request($request).decoded-content.&from-json;
}
Thanks for deconfusing me!
The following is not tested in situ with HTTP::Request
and a call to a google sheets API endpoint, but I'm sufficiently confident it'll work to post it as an answer:
my URI $uri .= new($.url) andthen .path(.path ~ ':clear');
my $request = HTTP::Request.new(POST => $uri, ...
A few notes about what I figured out on the way to the above solution might be useful for later readers:
This took me hours / days to figure out.
The community packages for handling this kind of stuff try their best to do generally useful things by default. In this case the default handling of colon in a path part of a URI (eg the foo/bar:baz
bit in http://example.com/foo.bar:baz#this?q=that
) is to percent encode it. But that means your HTTP::Request
call does not work (because the google sheets API requires that the colon remains as is).
The package doing this encoding is URI::Path
. It includes a Path.new
method which takes arguments including a match object containing a path capture from a Raku grammar parse of a URI, and an optional named argument scheme
. Given an HTTP scheme value for the latter, the path string is run through an unescape/escape filter -- which percent encodes characters such as colon. While this is generally helpful, it's also creating the problem you have. Fortunately, if Path.new
is not passed a named scheme
argument, then the path string is not passed through that filter. So calling Path.new
without the scheme
argument became the thing I aimed for.
Your code doesn't use URI::Path
directly. But it includes a POST
argument in a call of HTTP::Request.new
. The latter either creates a new URI
object from the argument's value (if it's a string) or uses the value as is (if it's already a URI
object). The solution in this answer uses this second option, preconstructing the URI
object in two steps. First, I create a new URI
object from an URL via URI.new
. This step would percent encode any colons in the path part, but I make sure the :clear
isn't included. Second, I: call URI
's zero argument .path
method (which returns the Path
object representing the path part of the URI
object, which stringifies to an ordinary string if need be); append ':clear'
to it; and I pass that final string to URI
's one argument .path
method, which updates the URI object's $!path
attribute by calling Path.new
omitting the scheme
named argument. This ensures the colon in the path is not percent encoded in the final URI
object.
I began to learn about what relevant aspects were supposed to look like from an API point of view when I read a key issue filed in 2015, Build a URI from parts which began:
It would be nice if you could build a URI from parts (and have it encode stuff properly without your involvement). If this is already possible, then it should be documented.
Reading the issue was an eye opener.
2 years later JonathanStowe++ committed code to close the issue. Doc commits followed.