Skip to main content

harmont_cloud/
error.rs

1//! Error type for the Harmont Cloud SDK.
2
3/// Errors surfaced by [`crate::HarmontClient`].
4#[derive(Debug, thiserror::Error)]
5pub enum HarmontError {
6    /// The request was not authenticated (no/invalid/expired token).
7    #[error("unauthorized — run `hm login` or set HARMONT_API_TOKEN")]
8    Unauthorized,
9
10    /// The API returned a non-2xx status with a structured error body.
11    #[error("api error {status} [{code}]: {message}")]
12    Api { status: u16, code: String, message: String },
13
14    /// Transport/connection failure.
15    #[error("network error: {0}")]
16    Transport(String),
17
18    /// Response body could not be decoded.
19    #[error("decode error: {0}")]
20    Decode(String),
21
22    /// SSE log stream framing error.
23    #[error("log stream error: {0}")]
24    LogStream(String),
25
26    /// The requested resource was not found (404).
27    #[error("not found: {0}")]
28    NotFound(String),
29}
30
31impl From<reqwest::Error> for HarmontError {
32    fn from(e: reqwest::Error) -> Self {
33        if e.is_decode() { HarmontError::Decode(e.to_string()) }
34        else { HarmontError::Transport(e.to_string()) }
35    }
36}
37
38pub type Result<T> = std::result::Result<T, HarmontError>;
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn api_error_carries_status_and_code() {
46        let e = HarmontError::Api { status: 422, code: "build_rejected".into(),
47                                    message: "pipeline_ir invalid".into() };
48        let s = e.to_string();
49        assert!(s.contains("422"));
50        assert!(s.contains("build_rejected"));
51        assert!(s.contains("pipeline_ir invalid"));
52    }
53
54    #[test]
55    fn unauthorized_is_distinguishable() {
56        let e = HarmontError::Unauthorized;
57        assert!(matches!(e, HarmontError::Unauthorized));
58    }
59}