Skip to main content

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}