Skip to main content

bob_rs/
error.rs

1//! Typed errors for the bob SDK.
2
3/// Why a bob-rs operation (install, keychain I/O, spawn) failed.
4///
5/// Each variant carries the real underlying error as a source — an
6/// [`std::io::Error`], a [`keyring::Error`], a [`serde_json::Error`], or a
7/// [`cli_stream::StreamError`] — rather than a pre-formatted string, so a
8/// consumer can inspect or downcast it (e.g. branch on `io::ErrorKind`, or
9/// recognise a `keyring::Error::NoEntry`). `#[non_exhaustive]` so adding a
10/// variant later isn't a breaking change.
11#[derive(Debug, thiserror::Error)]
12#[non_exhaustive]
13pub enum BobError {
14    /// An OS-level I/O failure while running the installer — spooling the
15    /// embedded script to a tempfile, setting its mode, spawning `bash`, or
16    /// waiting on it. `context` names the step; `source` is the OS error.
17    #[error("{context}: {source}")]
18    Io {
19        /// Which install step failed (e.g. `"spawn install script"`).
20        context: &'static str,
21        /// The underlying OS error.
22        #[source]
23        source: std::io::Error,
24    },
25
26    /// A spawned install child didn't expose a piped stdout/stderr. Shouldn't
27    /// happen given `Stdio::piped()`, but `Child`'s accessors return `Option`,
28    /// so the case is represented rather than `unwrap`ped.
29    #[error("install child {stream} pipe was not captured")]
30    PipeNotCaptured {
31        /// Which stream was missing — `"stdout"` or `"stderr"`.
32        stream: &'static str,
33    },
34
35    /// The OS keychain rejected a read / write / delete of the API key.
36    #[error("keychain access failed: {0}")]
37    Keychain(#[from] keyring::Error),
38
39    /// Serializing the on-disk auth-state marker failed.
40    #[error("auth-state serialization failed: {0}")]
41    Serialize(#[from] serde_json::Error),
42
43    /// A caller-supplied argument was invalid (e.g. an empty API key).
44    #[error("{0}")]
45    Invalid(String),
46
47    /// The Node.js runtime that would execute bob is missing or too old.
48    /// bob re-launches itself with flags that need a recent node (e.g.
49    /// `--disable-sigusr1`), so an old runtime dies with an opaque
50    /// "exited with code 9" — this preflight surfaces the real cause
51    /// (and the fix) *before* the spawn instead.
52    #[error("bob requires Node.js {minimum}+ — {detail}. Install Node {minimum} or newer (e.g. `nvm install {minimum}`), then reinstall or relaunch.")]
53    NodeIncompatible {
54        /// The minimum Node major bob supports (`BOB_MIN_NODE_VERSION`).
55        minimum: String,
56        /// What was actually found — a too-old version + its path, or
57        /// "no `node` found on PATH".
58        detail: String,
59    },
60
61    /// The platform application-data directory couldn't be resolved, so the
62    /// auth-state marker has nowhere to live.
63    #[error("could not determine the application data directory")]
64    NoDataDir,
65
66    /// The subprocess engine failed to spawn or cancel bob.
67    #[error(transparent)]
68    Stream(#[from] cli_stream::StreamError),
69}