The authenticated request helper shared by the google-* packages, plus
Google-shaped errors, retry classification, and nextPageToken
pagination.
Retry. google-core never waits between attempts. A connection
failure on an idempotent method (GET, HEAD, PUT, DELETE, OPTIONS) is
retried once, immediately (opts.retry: false turns that off).
Everything else, including 429, 5xx and rate-limit 403s, comes back
as err(HttpError) with retry-after set from the response's
Retry-After header. Check it with is-retryable and let Hot back
off: give the handler, schedule or task a retry policy and fail the
run (see https://hot.dev/docs/retries and is-retryable).
401 handling. When a cached token is rejected with 401, it is evicted and the call is retried once with a freshly minted token.
Values
IDEMPOTENT_METHODS
IDEMPOTENT_METHODS: Vec [
"GET",
"HEAD",
"PUT",
"DELETE",
"OPTIONS"
]
MAX_IMMEDIATE_RETRIES
MAX_IMMEDIATE_RETRIES: Int 1
MAX_PAGES
MAX_PAGES: Int 10000
RETRY_REASONS
RETRY_REASONS: Vec [
"rateLimitExceeded",
"userRateLimitExceeded"
]
RETRY_STATUSES
RETRY_STATUSES: Vec [
408,
429,
500,
502,
503,
504
]
Functions
api-params
fn (opts: Map): Map
Strip google-core's own keys (::google::auth/CREDENTIAL_KEYS: credentials, scopes, retry) from an opts Map, leaving only API parameters.
body-excerpt
fn (body: Any): Str
A short (at most 300 characters) printable form of a response body, for error messages.
build-http-request
fn (method: Str, url: Str, headers: Map, body: Any, token: Str?): ::hot::http/HttpRequest
Build the outgoing HttpRequest. token (nullable) becomes the
Bearer header. Map/Vec bodies are JSON-encoded with a JSON
Content-Type; Str/Bytes bodies are sent verbatim (set Content-Type in
headers); "" or null sends no body. Caller headers win.
collect-loop
fn (fetch: Fn, items-key: Str, limit: Int?, page-token: Str?, acc: Vec, pages: Int): Vec
collect-pages
fn (fetch: Fn, items-key: Str): Vec
fn (fetch: Fn, items-key: Str, limit: Int?): Vec
Follow nextPageToken and collect every item, or at most limit
items (null for no limit). fetch and items-key are as for
fetch-page. The first failing page's Err is returned.
files ::google::api/collect-pages((t) {
::google::drive::files/list-files({q: "trashed = false", pageToken: t, pageSize: 1000})
}, "files", 5000)
credential-opts
fn (opts: Map): Map
Keep only google-core's own keys (credentials, scopes, retry) from an opts Map, e.g. to pass them on to a follow-up call.
decode-error-body
fn (body: Any): Any
fetch-page
fn (fetch: Fn, items-key: Str, page-token: Str?): Page
Fetch one page. fetch is (page-token: Str?) -> Map (null for the
first page) returning the decoded list response; items-key names its
list field ("files", "drives", "permissions", ...).
page ::google::api/fetch-page((t) { ::google::drive::files/list-files({pageToken: t}) }, "files", null)
page.items
page.next-page-token
google-error-details
fn (body: Any): Map
Extract Google's error fields from a response body: {message, status, reasons}. Handles the standard {error: {code, message, status, errors: [{reason, ...}]}} shape and the OAuth-style
{error: "invalid_grant", error_description} shape. Fields are null
(and reasons empty) when absent.
has-reason
fn (error: Result, reason: Str): Bool
True when error (an HttpError, a Result.Err holding one, or any error payload) carries the given Google reason.
is-connection-failure
fn (message: Str): Bool
True when a transport error message is a connection or read failure (worth trying again), not a size-limit or URL error.
is-retryable
fn (error: Result): Bool
True when a failed call is worth retrying later: an HttpError (or a
Result.Err holding one, or a raw HttpResponse) with status 408,
429, 500, 502, 503 or 504, or a 403 rate-limit reason; a
TokenError from the token endpoint with one of those statuses; or
a connection failure. False for successes and for permanent errors
(400, 401, 403 permission, 404, ...).
google-core does not wait and retry for you: Hot has no in-run sleep.
Let Hot reschedule the work instead. Give the handler, schedule or
task a retry policy (see https://hot.dev/docs/retries) and fail
the run when this returns true; retry-after on the HttpError
says how long Google asked you to wait.
sync-inbox
meta {
schedule: "every 15 minutes",
retry: {attempts: 5, delay: 2000, backoff: "exponential", max_delay: 60000, jitter: true}
}
fn (event) {
page ::google::api/json("gmail.readonly", "GET", "https://gmail.googleapis.com/gmail/v1/users/me/messages", {}, "")
cond {
::google::api/is-retryable(page) => { fail(`google: retrying later: ${err-message(page)}`) }
is-err(page) => { println(`google: giving up: ${err-message(page)}`) }
=> { length(or(page.messages, [])) }
}
}
A connection failure on a non-idempotent call (sending mail, creating an event) may still have reached Google; retrying it can duplicate the write.
is-retryable-status
fn (status: Int, body: Any): Bool
True for statuses Google asks clients to retry later: 408, 429, 5xx (500/502/503/504), and 403 with reason rateLimitExceeded / userRateLimitExceeded.
is-retryable-transport
fn (method: Str, message: Str): Bool
True when a transport failure may be retried immediately: an idempotent method (GET, HEAD, PUT, DELETE, OPTIONS) and a connection failure.
json
fn (scope: Str | Vec, method: Str, url: Str): Any
fn (scope: Str | Vec, method: Str, url: Str, additional-headers: Map, body: Any): Any
fn (scope: Str | Vec, method: Str, url: Str, additional-headers: Map, body: Any, token: Str): Any
fn (scope: Str | Vec, method: Str, url: Str, additional-headers: Map, body: Any, opts: Map): Any
Like request, but unwraps: the decoded JSON body on 2xx, or an Err carrying HttpError (credential failures carry ::google::auth/TokenError). The shape used by all google-* endpoint functions. Takes the same arities as request.
parse-int
fn (value: Any): Int?
Parse an Int from an Int or a decimal-digit Str (surrounding whitespace allowed); null for anything else.
plain-payload
fn (value: Result): Any
The payload of a Result.Err (or the value itself), with type tags removed so an HttpError / TokenError reads like a Map.
request
fn (scope: Str | Vec, method: Str, url: Str): ::hot::http/HttpResponse
fn (scope: Str | Vec, method: Str, url: Str, additional-headers: Map, body: Any): ::hot::http/HttpResponse
fn (scope: Str | Vec, method: Str, url: Str, additional-headers: Map, body: Any, token: Str): ::hot::http/HttpResponse
fn (scope: Str | Vec, method: Str, url: Str, additional-headers: Map, body: Any, opts: Map): ::hot::http/HttpResponse
Make an authenticated request to a Google API and return the raw
HttpResponse (non-2xx included), or an Err for credential and
transport failures.
scope- the scope(s) the call needs (Str or Vec, short names ok); overridden byopts.scopes/google.scopesadditional-headers- extra headers (they win over the defaults)body- a Map/Vec (sent as JSON), Str/Bytes (sent verbatim), or""opts(6th argument) - a Map of per-call credentials (token,refresh-token+client-id+client-secret,service-account+subject,api-key),scopes, andretry(falsedisables the one immediate retry of a failed connection). A Str 6th argument is an explicit access token (kept for compatibility). Per-call credentials are masked in call records, and one that is present but blank or null is an Err naming the key.
Example
response ::google::api/request("drive.readonly", "GET",
"https://www.googleapis.com/drive/v3/about?fields=user", {}, "")
// As a specific tenant user, without touching context
response2 ::google::api/request("drive.readonly", "GET",
"https://www.googleapis.com/drive/v3/about?fields=user", {}, "",
{token: "ya29.tenant-access-token"})
retry-after-seconds
fn (headers: Map?): Int?
The Retry-After header of a response's headers as whole seconds (Int), or null when absent or given as an HTTP date.
retry-policy
fn (opts: Map): Map
The immediate-retry policy for a call (pure): {max-retries: 1}, or
{max-retries: 0} when opts.retry is false or 0. An Int or
{max-retries} Map is accepted and clamped to 0..1: google-core
never waits between attempts, so it never retries more than once.
send-with-retry
fn (req: ::hot::http/HttpRequest, policy: Map): ::hot::http/HttpResponse
fn (req: ::hot::http/HttpRequest, policy: Map, attempt: Int): ::hot::http/HttpResponse
Send an HttpRequest; on a connection failure of an idempotent method, send it once more straight away (per policy). Never waits. Returns the final response or transport Err.
unwrap
fn (response: Result): Any
The decoded body of a 2xx response, the Err of a failed request, or err(HttpError) for a non-2xx response.
with-query
fn (url: Str, params: Map): Str
Append query parameters to a URL, dropping null values; Bools encode
as true/false. A Vec value repeats the key once per element
({labelIds: ["INBOX", "UNREAD"]} gives
labelIds=INBOX&labelIds=UNREAD), the way Google's repeated
parameters expect. Works whether or not the URL already has a query.
Types
HttpError
HttpError type {
status: Int,
headers: Map?,
body: Any,
message: Str?,
reason: Str?,
reasons: Vec?,
error-status: Str?,
error-message: Str?,
retry-after: Int?
}
A non-2xx response from a Google API: HttpError(response) builds it
from the HttpResponse of request.
Fields
status- HTTP status codeheaders,body- the raw response (body is decoded JSON when JSON)message- readable summary, e.g."google: HTTP 404 NOT_FOUND (notFound): File not found: 1AbC."error-message- Google'serror.messageerror-status- Google'serror.status(e.g."PERMISSION_DENIED")reason- the firsterror.errors[].reason(e.g."notFound","insufficientFilePermissions","rateLimitExceeded")reasons- everyerror.errors[].reasonretry-after- seconds from a numericRetry-Afterheader, else null
Page
Page type {
items: Vec,
next-page-token: Str?,
response: Map
}
One page of a list call: items (the list under the endpoint's items key), next-page-token (null on the last page), and the full decoded response.