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.