main
rs 65 lines 2.08 KB
Raw
1 //! Errors surfaced by the sigit.si API client.
2
3 /// The API's error body — `Api::BaseController#render_error` renders every
4 /// failure as `{ "error_code": <i32>, "message": <string> }`.
5 #[derive(Debug, Clone, serde::Deserialize)]
6 pub struct ErrorResponse {
7 pub error_code: i32,
8 pub message: String,
9 }
10
11 /// `error_codes::ErrorCode` values the server sends, mirrored from
12 /// `Api::BaseController`.
13 pub mod codes {
14 pub const UNKNOWN: i32 = 0;
15 pub const UNAUTHORIZED: i32 = 100;
16 pub const INVALID: i32 = 101;
17 }
18
19 pub type Result<T> = std::result::Result<T, ApiError>;
20
21 #[derive(Debug, thiserror::Error)]
22 pub enum ApiError {
23 /// The token is missing, expired, or rejected. Callers treat this as
24 /// "clear the stored token and ask the user to sign in again", so it stays
25 /// a distinct variant rather than folding into [`ApiError::Status`].
26 #[error("Your session has expired. Sign in again with `si auth login`.")]
27 Unauthorized,
28
29 /// No token on disk for this environment.
30 #[error("You are not signed in. Run `si auth login` first.")]
31 NotAuthenticated,
32
33 /// The server answered with a non-2xx status and an `ErrorResponse` body.
34 #[error("{message}")]
35 Status {
36 status: u16,
37 error_code: i32,
38 message: String,
39 },
40
41 /// An MCP tool reported an in-band failure (`isError: true`). Issues and
42 /// pull requests go over MCP, so this is a normal user-facing error.
43 #[error("{0}")]
44 Tool(String),
45
46 /// The request never completed — DNS, TLS, connection refused, timeout.
47 #[error("Could not reach {host}: {source}")]
48 Transport {
49 host: String,
50 #[source]
51 source: reqwest::Error,
52 },
53
54 /// A 2xx body that didn't match the expected shape.
55 #[error("Unexpected response from the server: {0}")]
56 Parse(String),
57 }
58
59 impl ApiError {
60 /// True when the caller should discard the stored credentials.
61 pub fn is_auth_failure(&self) -> bool {
62 matches!(self, ApiError::Unauthorized | ApiError::NotAuthenticated)
63 || matches!(self, ApiError::Status { status: 401, .. })
64 }
65 }