Skip to main content

anakin/
error.rs

1use std::time::Duration;
2
3use thiserror::Error;
4
5/// All errors returned by the Anakin SDK. Match on a specific variant to
6/// react to a particular failure mode (e.g. [`Error::InsufficientCredits`]),
7/// or treat the enum as opaque for a generic fallback.
8#[derive(Debug, Error)]
9pub enum Error {
10    /// 401 — invalid or missing API key.
11    #[error("authentication failed: {message}")]
12    Authentication {
13        message: String,
14        status: u16,
15        code: Option<String>,
16    },
17
18    /// 402 — out of credits. `balance` is the caller's current balance,
19    /// `required` is the credit cost the failed request would have incurred.
20    #[error("insufficient credits: balance={balance}, required={required} ({message})")]
21    InsufficientCredits {
22        message: String,
23        status: u16,
24        code: Option<String>,
25        balance: i64,
26        required: i64,
27    },
28
29    /// 400 — validation failure.
30    #[error("invalid request: {message}")]
31    InvalidRequest {
32        message: String,
33        status: u16,
34        code: Option<String>,
35    },
36
37    /// 429 — rate limited after the SDK exhausted its retry budget.
38    /// `retry_after` is the duration the API asked the client to wait.
39    #[error("rate limited (retry_after={retry_after:?}): {message}")]
40    RateLimit {
41        message: String,
42        status: u16,
43        code: Option<String>,
44        retry_after: Duration,
45    },
46
47    /// Polled job came back with status="failed".
48    #[error("job failed: {reason} (job_id={job_id:?})")]
49    JobFailed {
50        job_id: Option<String>,
51        reason: String,
52    },
53
54    /// Polling budget exhausted before the job reached a terminal status.
55    #[error("polling timed out after {elapsed:?} (job_id={job_id:?})")]
56    JobTimeout {
57        job_id: Option<String>,
58        elapsed: Duration,
59    },
60
61    /// 5xx after retries were exhausted.
62    #[error("server error {status}: {message}")]
63    Server {
64        message: String,
65        status: u16,
66        code: Option<String>,
67    },
68
69    /// DNS / connect / read-timeout failure from the underlying HTTP client.
70    #[error("network error: {message}")]
71    Network {
72        message: String,
73        #[source]
74        source: Option<Box<dyn std::error::Error + Send + Sync>>,
75    },
76
77    /// Any other error: serialization, unexpected response shape, etc.
78    #[error("anakin: {0}")]
79    Other(String),
80}
81
82impl Error {
83    /// HTTP status code if this came from the API, else 0.
84    pub fn status(&self) -> u16 {
85        match self {
86            Error::Authentication { status, .. }
87            | Error::InsufficientCredits { status, .. }
88            | Error::InvalidRequest { status, .. }
89            | Error::RateLimit { status, .. }
90            | Error::Server { status, .. } => *status,
91            _ => 0,
92        }
93    }
94
95    /// API machine-readable error code if present.
96    pub fn code(&self) -> Option<&str> {
97        match self {
98            Error::Authentication { code, .. }
99            | Error::InsufficientCredits { code, .. }
100            | Error::InvalidRequest { code, .. }
101            | Error::RateLimit { code, .. }
102            | Error::Server { code, .. } => code.as_deref(),
103            _ => None,
104        }
105    }
106}
107
108/// Convenient alias used throughout the crate.
109pub type Result<T> = std::result::Result<T, Error>;