Skip to main content

cliban_sync/
error.rs

1//! Error vocabulary for the sync bridges.
2//!
3//! Kept separate from [`cliban_core::Error`] because the failure modes are
4//! different in kind: the core fails on validation and storage, a bridge fails
5//! on credentials, network, and a remote API disagreeing with us. The CLI maps
6//! these onto cliban's exit codes (1 not-found, 2 validation, 3 other).
7
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum Error {
12    /// No API token in the environment. Named explicitly because it is by far
13    /// the most common first-run failure and the fix is one line.
14    #[error("{0} is not set; export a Linear API key (https://linear.app/settings/api)")]
15    MissingToken(&'static str),
16
17    /// The remote rejected our credentials.
18    #[error("Linear rejected the API key (HTTP {0})")]
19    Unauthorized(u16),
20
21    /// The remote returned GraphQL-level errors. Carries their messages
22    /// verbatim — they are usually actionable and we have nothing to add.
23    #[error("Linear API error: {}", .0.join("; "))]
24    Api(Vec<String>),
25
26    /// A lookup found nothing. `what` reads like `issue ENG-412` or `team ENG`.
27    #[error("not found: {0}")]
28    NotFound(String),
29
30    /// The remote's shape did not match what we asked for. Distinct from
31    /// [`Error::Api`]: this one means our query and our structs disagree,
32    /// which is a cliban bug rather than a user error.
33    #[error("unexpected response from Linear: {0}")]
34    Unexpected(String),
35
36    /// A local edit would clobber a newer remote change.
37    #[error(
38        "{remote_key} changed in Linear since the last sync \
39         (remote {remote}, ours {ours}); re-import first, or pass --force"
40    )]
41    StaleWrite {
42        remote_key: String,
43        remote: String,
44        ours: String,
45    },
46
47    /// The config file exists but does not parse or does not make sense.
48    #[error("config: {0}")]
49    Config(String),
50
51    #[error("HTTP transport: {0}")]
52    Http(String),
53
54    #[error(transparent)]
55    Core(#[from] cliban_core::Error),
56
57    #[error(transparent)]
58    Sqlite(#[from] rusqlite::Error),
59
60    #[error(transparent)]
61    Json(#[from] serde_json::Error),
62}
63
64impl From<reqwest::Error> for Error {
65    fn from(e: reqwest::Error) -> Self {
66        Error::Http(e.to_string())
67    }
68}
69
70pub type Result<T> = std::result::Result<T, Error>;