Error variants¶
Every fallible operation in the crate returns CredentialError. It
derives Debug, Clone, Error (via thiserror) and Diagnostic
(via miette), and is #[non_exhaustive].
The five variants¶
| Variant | Message | Diagnostic code |
|---|---|---|
NotFound { name: String } |
credential not found: {name} |
rtb::credentials::not_found |
LiteralRefusedInCi |
literal credential is refused in CI environments |
rtb::credentials::literal_refused |
Keychain(String) |
keychain backend error: {0} |
rtb::credentials::keychain |
ReadOnly |
this credential store is read-only |
rtb::credentials::read_only |
Io(Arc<std::io::Error>) |
I/O error: {0} |
rtb::credentials::io |
Because the enum is #[non_exhaustive], a downstream match needs a
wildcard arm, and new variants can be added without a major version
bump.
NotFound — nothing in the chain produced a value¶
Returned by Resolver::resolve when every configured leg missed, and by
MemoryStore, EnvStore and KeyringStore when the requested entry is
absent.
The name field is a diagnostic label, not a machine-readable key. What
it contains depends on the producer:
| Producer | name |
|---|---|
MemoryStore / KeyringStore |
"{service}/{account}" |
EnvStore |
the environment variable name |
Resolver::resolve |
fallback_env, else env, else "{service}/{account}", else <unnamed credential> |
NotFound from a store during the resolver's keychain leg is not
surfaced to the caller — the resolver treats it as a miss and continues
down the chain.
LiteralRefusedInCi — a literal secret was declined under CI¶
Returned by Resolver::resolve when cref.literal is set and the CI
environment variable is exactly "true".
The variant carries no payload, and the resolver returns it instead of
continuing to the fallback_env leg.
Its miette help text is:
This is a policy refusal, not a technical failure. The secret was present and readable; the crate declined to hand it over.
Keychain(String) — the backend failed, or a lock was poisoned¶
Wraps a message from the platform keychain. Produced by KeyringStore
for any keyring error other than "no entry", including a failure to
construct the entry, and for a spawn_blocking join failure — the
latter formatted as join error: {e}.
MemoryStore also returns this variant, with the message
in-memory lock poisoned, when its RwLock has been poisoned by a
panic in another thread. There is no separate variant for that case, so
matching on Keychain does not prove a keychain was involved.
The inner String is a rendered message. It is not structured and
should not be parsed to distinguish causes.
ReadOnly — the store does not support mutation¶
Returned by EnvStore::set, EnvStore::delete, LiteralStore::set and
LiteralStore::delete.
It signals a permanent property of the store, not a transient condition
or a permissions problem — retrying, or running with more privilege,
will not change the outcome. MemoryStore and KeyringStore never
return it.
Io — reserved, and never produced by this crate¶
Io exists and converts from std::io::Error through a From impl:
impl From<std::io::Error> for CredentialError {
fn from(e: std::io::Error) -> Self {
Self::Io(Arc::new(e))
}
}
No built-in store returns it. Nothing in the crate constructs Io
outside that conversion, so matching on it while using only the four
supplied stores matches a branch that cannot currently be reached.
It is there for downstream CredentialStore implementations — a
file-backed or network-backed store can use ? on an I/O operation and
get the conversion for free.
Why the enum is Clone when io::Error is not¶
std::io::Error does not implement Clone, which would normally make
any enum containing it non-Clone. The Io variant wraps its error in
an Arc specifically to avoid that.
The reason is fan-out. One credential failure often needs to reach
several consumers at once — a log line, a telemetry event, a
health-check aggregate — and a Clone-able error lets each take a copy
instead of the producer having to rebuild an equivalent error per
consumer or hand out references with awkward lifetimes.
The cost is that cloning an Io error shares one underlying
io::Error rather than duplicating it. Since the error is only ever
read, that is invisible in practice.
Rendering an error to a user¶
CredentialError implements miette::Diagnostic, so a tool whose main
returns miette::Result gets the code and help text rendered without
extra work. The miette dependency is declared with the fancy feature,
which pulls in the graphical report renderer.
fn main() -> miette::Result<()> {
// a CredentialError propagated with `?` renders with its
// rtb::credentials::* code and any help text attached
Ok(())
}
Only LiteralRefusedInCi carries help text. The other four variants
have a diagnostic code but no help, so they render as the message and
code alone.