rtb_credentials/error.rs
1//! Typed errors for the credentials subsystem.
2
3use std::sync::Arc;
4
5use miette::Diagnostic;
6use thiserror::Error;
7
8/// Failures surfaced by credential retrieval / storage.
9///
10/// `Clone` is derived — the `Io` variant wraps its `std::io::Error`
11/// in an `Arc` precisely to keep the enum cloneable, so subsystems
12/// that fan errors out to multiple consumers (telemetry, logs,
13/// health-check aggregation) don't have to allocate a new enum per
14/// consumer.
15#[derive(Debug, Clone, Error, Diagnostic)]
16#[non_exhaustive]
17pub enum CredentialError {
18 /// No credential found at the named location.
19 #[error("credential not found: {name}")]
20 #[diagnostic(code(rtb::credentials::not_found))]
21 NotFound {
22 /// Diagnostic-friendly name (env var name, keychain
23 /// service/account, or a caller-supplied label).
24 name: String,
25 },
26
27 /// A literal credential was rejected because the process is
28 /// running under CI.
29 ///
30 /// Detection today is `CI=true` only — the common convention used
31 /// by `GitHub` Actions, `GitLab` CI, `CircleCI`, Buildkite, and others.
32 /// This is a deliberate pragma: broader detection (`CI_*` globs,
33 /// provider-specific env vars) produces false positives for
34 /// developer shells that happen to export `CI=1` or `CI_SERVER`.
35 /// Tools that want stricter enforcement can set the variable
36 /// themselves before calling [`crate::Resolver::resolve`].
37 #[error("literal credential is refused in CI environments")]
38 #[diagnostic(
39 code(rtb::credentials::literal_refused),
40 help("set CI=false locally, or move the secret to a keychain/env var")
41 )]
42 LiteralRefusedInCi,
43
44 /// Keychain backend returned an error.
45 #[error("keychain backend error: {0}")]
46 #[diagnostic(code(rtb::credentials::keychain))]
47 Keychain(String),
48
49 /// The backing store does not support the requested mutation
50 /// (e.g. `EnvStore::set` — env mutation is explicitly out of
51 /// scope; `set_var` requires `unsafe` for soundness).
52 #[error("this credential store is read-only")]
53 #[diagnostic(code(rtb::credentials::read_only))]
54 ReadOnly,
55
56 /// Filesystem / I/O failure while interacting with the store.
57 /// The inner error is `Arc`-wrapped so `CredentialError` can
58 /// remain `Clone`.
59 #[error("I/O error: {0}")]
60 #[diagnostic(code(rtb::credentials::io))]
61 Io(Arc<std::io::Error>),
62}
63
64impl From<std::io::Error> for CredentialError {
65 fn from(e: std::io::Error) -> Self {
66 Self::Io(Arc::new(e))
67 }
68}