Skip to main content

rtb_update/
error.rs

1//! The `UpdateError` enum.
2
3use std::sync::Arc;
4
5/// Every failure mode the self-update flow can surface.
6///
7/// `Clone` is derived so callers can route errors through retry
8/// policies or embed them in progress events without losing the
9/// underlying `io::Error`. The `Io` variant wraps in `Arc` — same
10/// pattern as `rtb-forge::ProviderError` and `rtb-credentials::CredentialError`.
11#[derive(Debug, thiserror::Error, miette::Diagnostic, Clone)]
12#[non_exhaustive]
13pub enum UpdateError {
14    /// The upstream [`rtb_forge::ProviderError`] surfaced a failure.
15    #[error(transparent)]
16    #[diagnostic(transparent)]
17    Provider(#[from] rtb_forge::ProviderError),
18
19    /// No asset on the release matched the host platform.
20    #[error("no asset found for target {target}")]
21    #[diagnostic(
22        code(rtb::update::no_matching_asset),
23        help("the release exists but has no asset for this platform; a rebuild may be needed")
24    )]
25    NoMatchingAsset {
26        /// The host target triple we tried to match.
27        target: String,
28    },
29
30    /// Required signature file was absent from the release.
31    #[error("asset signature file missing (expected `{asset}.sig` or `{asset}.minisig`)")]
32    #[diagnostic(
33        code(rtb::update::missing_signature),
34        help(
35            "every published release must ship a detached signature; re-run the release pipeline"
36        )
37    )]
38    MissingSignature {
39        /// The asset filename we looked for a signature for.
40        asset: String,
41    },
42
43    /// Ed25519 signature did not verify against any trusted public key.
44    #[error("signature verification failed for `{asset}`")]
45    #[diagnostic(
46        code(rtb::update::bad_signature),
47        help(
48            "the downloaded bytes do not match the vendor's public key — treat as a potential tampering event"
49        )
50    )]
51    BadSignature {
52        /// The asset filename whose signature failed.
53        asset: String,
54    },
55
56    /// SHA-256 checksum did not match the checksums asset.
57    #[error("SHA-256 checksum mismatch for `{asset}`")]
58    #[diagnostic(code(rtb::update::bad_checksum))]
59    BadChecksum {
60        /// The asset filename whose checksum failed.
61        asset: String,
62    },
63
64    /// The staged binary refused `--version` (or did not match the
65    /// release tag). Swap is refused.
66    #[error("downloaded binary failed the runnable-self-test")]
67    #[diagnostic(
68        code(rtb::update::self_test_failed),
69        help("the new binary refused `--version`; refusing to swap")
70    )]
71    SelfTestFailed,
72
73    /// `self-replace` failed to swap.
74    #[error("atomic swap failed: {0}")]
75    #[diagnostic(code(rtb::update::swap_failed))]
76    SwapFailed(String),
77
78    /// `ToolMetadata::release_source` is `None` — the tool has not
79    /// been configured for self-update.
80    #[error("tool metadata carries no release source; update disabled")]
81    #[diagnostic(code(rtb::update::no_source))]
82    NoReleaseSource,
83
84    /// Every entry in `ToolMetadata::update_public_keys` failed to
85    /// parse as a minisign public key. Distinguished from
86    /// `BadSignature` because the fault is in the binary's own
87    /// compiled-in trust set, not in the downloaded asset — reporting
88    /// it as a signature failure would send an operator hunting for
89    /// tampering that has not happened.
90    #[error("no usable public key: every entry in the trust set failed to parse")]
91    #[diagnostic(
92        code(rtb::update::malformed_public_key),
93        help(
94            "`ToolMetadata::update_public_keys` takes minisign public keys — the base64 string from a minisign.pub, e.g. \"RWR…\""
95        )
96    )]
97    MalformedPublicKey,
98
99    /// `ToolMetadata::update_public_keys` is empty — signatures cannot
100    /// be verified so updates are refused as a security policy.
101    #[error("tool metadata carries no public key; signatures cannot be verified")]
102    #[diagnostic(
103        code(rtb::update::no_public_key),
104        help("populate `ToolMetadata::update_public_keys` at compile time")
105    )]
106    NoPublicKey,
107
108    /// The caller asked for a downgrade (`target < current`) without
109    /// `--force`. Guards against a bad `--to` value turning into a
110    /// permanent regression.
111    #[error("downgrade refused: target {target} is older than current {current}")]
112    #[diagnostic(
113        code(rtb::update::downgrade_refused),
114        help("pass `--force` to explicitly downgrade")
115    )]
116    DowngradeRefused {
117        /// The version the caller requested.
118        target: semver::Version,
119        /// The version currently installed.
120        current: semver::Version,
121    },
122
123    /// Archive extraction failed. Includes tar/gzip errors.
124    #[error("archive extraction failed: {0}")]
125    #[diagnostic(code(rtb::update::archive))]
126    Archive(String),
127
128    /// Asset pattern had a `{version}` placeholder but no value to
129    /// fill, or matched zero assets.
130    #[error("asset pattern invalid or unmatched: {0}")]
131    #[diagnostic(code(rtb::update::pattern))]
132    Pattern(String),
133
134    /// I/O error during cache-dir or swap step.
135    #[error("I/O error: {0}")]
136    #[diagnostic(code(rtb::update::io))]
137    Io(#[from] Arc<std::io::Error>),
138}
139
140impl From<std::io::Error> for UpdateError {
141    fn from(err: std::io::Error) -> Self {
142        Self::Io(Arc::new(err))
143    }
144}
145
146/// `Result<T, UpdateError>`.
147pub type Result<T> = std::result::Result<T, UpdateError>;