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>;