ProviderError variants¶
Every ReleaseProvider method returns Result<_, ProviderError>. The enum is
#[non_exhaustive] — match with a _ arm — and derives Clone so an error can
be stashed in a retry policy or a rendered diagnostic without losing the
underlying io::Error.
How an HTTP response becomes an error¶
Every REST backend routes non-2xx responses through one shared mapping, in this order:
| Condition | Variant |
|---|---|
| status is 2xx | Ok(()) |
status is 429 Too Many Requests |
RateLimited |
GitHub only: 403 and X-RateLimit-Remaining: 0 |
RateLimited |
status is 401 Unauthorized |
Unauthorized |
status is 404 Not Found |
NotFound |
| anything else | Transport |
The last row is the one that surprises people: a plain 403 Forbidden, a
500, and a 502 all arrive as Transport, not as Unauthorized or a
dedicated server-error variant. A GitHub token with insufficient scopes
produces a 403 without rate-limit headers, so it reads as a transport
failure. A GitLab or Gitea token that is merely under-scoped often produces a
404 instead, because those hosts hide resources you cannot see.
NotFound¶
Diagnostic code rtb::vcs::not_found.
Produced by a 404, and by two backend-specific cases:
- GitLab
latest_releasewhen the newest release is an upcoming release —whatis the literal stringlatest release. - Bitbucket
latest_releaseon a repository with no tags —whatisno tags available for latest release. - Direct
release_by_tagwhenpinned_versionis set and the requested tag does not equal it —whatis the requested tag.
For a 404, what is the status line rather than the thing you asked for
(404 Not Found). Do not parse it; keep your own record of what you requested.
A draft release requested without a token also lands here: the host returns a
404 rather than admitting the release exists.
Unauthorized¶
Diagnostic code rtb::vcs::unauthorized, with the help text "check the
credential registered for this release source".
Produced only by a 401. The host field is the normalised host the provider
was built with, not the full request URL.
RateLimited¶
Diagnostic code rtb::vcs::rate_limited.
retry_after: Option<Duration> is filled in from the response headers:
Retry-After, if it parses as an integer number of seconds. ARetry-Afterin HTTP-date form is not understood and is skipped.- Otherwise
X-RateLimit-Reset, read as Unix epoch seconds, and only if it is in the future. The duration is the difference from now. - Otherwise
None.
None means "the host did not tell us", not "retry immediately". Apply your own
backoff.
Transport¶
Diagnostic code rtb::vcs::transport. Two quite different situations produce
it:
- The request never completed — DNS failure, TLS failure, connection refused,
or the configured
timeout_secondselapsing. The payload isreqwest's stringified error. - The response arrived with a status the mapping does not recognise. The
payload is
unexpected status {status} from {host}.
Check for that unexpected status prefix if you need to tell a server error
apart from a network error.
MalformedResponse¶
Diagnostic code rtb::vcs::malformed_response. The host answered with 2xx and
a body that did not deserialise into the expected shape. Most often a proxy or
captive portal returning an HTML error page with a 200.
The Direct backend adds two of its own:
Unsupported¶
Diagnostic code rtb::vcs::unsupported, with help text pointing at
latest_release / release_by_tag.
Produced by exactly one call in the crate: list_releases on the Bitbucket
backend. Bitbucket Cloud has no list-releases endpoint, and synthesising one
would mean a downloads lookup per tag. There is no configuration that makes
Bitbucket list_releases work.
The variant carries no fields, so a tool that supports several backends should treat it as "this capability is absent here" and degrade, not retry.
InvalidConfig¶
Diagnostic code rtb::vcs::invalid_config. Raised by a factory before any
network call. The full set of messages and their triggers is in
Release source configuration. The
categories are:
- A required parameter is empty or whitespace.
- A
hostorversion_urluseshttp://where HTTPS is required. - A
directtemplate lacks{version}. - A factory was handed a config for a different backend.
reqwestcould not build a client —reqwest build failed: {e}. This is a TLS-stack problem, not a config typo.
An InvalidConfig never becomes valid by retrying.
Io¶
Diagnostic code rtb::vcs::io. Wraps Arc<std::io::Error> — the Arc is what
keeps the whole enum Clone without discarding the error kind. There is a
From<std::io::Error> impl, so ? on an I/O error inside a custom backend
produces this variant.
Cloning a ProviderError::Io shares the same Arc; both copies report the
same ErrorKind.
Which variants can each backend produce?¶
| Variant | GitHub | GitLab | Gitea / Codeberg | Bitbucket | Direct |
|---|---|---|---|---|---|
NotFound |
yes | yes | yes | yes | yes |
Unauthorized |
yes | yes | yes | yes | yes |
RateLimited |
yes (429 and 403) | yes (429) | yes (429) | yes (429) | yes (429) |
Transport |
yes | yes | yes | yes | yes |
MalformedResponse |
yes | yes | yes | yes | yes |
Unsupported |
no | no | no | list_releases only |
no |
InvalidConfig |
factory | factory | factory | factory | factory |
Io |
only from custom backends |