Skip to main content

babelforce_manager_sdk/
error.rs

1use serde_json::Value;
2
3/// The error type returned by every facade call.
4#[derive(Debug, thiserror::Error)]
5pub enum ManagerError {
6    /// A non-2xx response from the manager API.
7    #[error("manager API error {status}: {message}")]
8    Api {
9        /// HTTP status code of the response.
10        status: u16,
11        /// Machine-readable error code from the response envelope, if present.
12        code: Option<String>,
13        /// Human-readable message extracted from the response envelope.
14        message: String,
15        /// Raw response body.
16        body: String,
17    },
18    /// A transport/network failure.
19    #[error("network error: {0}")]
20    Network(String),
21    /// A response that could not be decoded.
22    #[error("decode error: {0}")]
23    Decode(String),
24    /// An argument supplied to the SDK was invalid (e.g. a malformed id).
25    #[error("invalid argument: {0}")]
26    InvalidArgument(String),
27}
28
29impl ManagerError {
30    /// Build an [`ManagerError::Api`] from a non-2xx status + raw body, extracting `code`/`message`
31    /// from the JSON envelope when present (mirrors the TS/Go facades).
32    pub(crate) fn from_response(status: u16, body: String) -> Self {
33        let mut code = None;
34        let mut message = String::new();
35        if let Ok(v) = serde_json::from_str::<Value>(&body) {
36            code = v.get("code").and_then(Value::as_str).map(str::to_string);
37            for k in ["message", "error", "detail"] {
38                if let Some(m) = v.get(k).and_then(Value::as_str) {
39                    if !m.is_empty() {
40                        message = m.to_string();
41                        break;
42                    }
43                }
44            }
45        }
46        if message.is_empty() {
47            message = format!("HTTP {status}");
48        }
49        ManagerError::Api {
50            status,
51            code,
52            message,
53            body,
54        }
55    }
56}
57
58// Each generated module exposes its own (structurally identical) `Error<T>`. The facade needs, per
59// module: a `map_*_err` (gen Error -> ManagerError) and a `Transient` impl (retry classification).
60// These are written explicitly per module (macros can't extend a `:path` metavar with `::Variant`
61// in pattern position). Add the matching pair when a new spec's first domain is wrapped.
62
63pub(crate) fn map_manager_err<T>(e: crate::gen::manager::apis::Error<T>) -> ManagerError {
64    use crate::gen::manager::apis::Error;
65    match e {
66        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
67        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
68        Error::Serde(err) => ManagerError::Decode(err.to_string()),
69        Error::Io(err) => ManagerError::Network(err.to_string()),
70    }
71}
72
73impl<T> crate::retry::Transient for crate::gen::manager::apis::Error<T> {
74    fn transient(&self, idempotent: bool) -> bool {
75        use crate::gen::manager::apis::Error;
76        match self {
77            Error::Reqwest(_) | Error::Io(_) => idempotent,
78            Error::ResponseError(rc) => {
79                let s = rc.status.as_u16();
80                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
81            }
82            _ => false,
83        }
84    }
85}
86
87// The facade's raw-JSON read path (see `resources/applications.rs`) yields `ManagerError`
88// directly — same retry semantics as the generated errors above.
89impl crate::retry::Transient for ManagerError {
90    fn transient(&self, idempotent: bool) -> bool {
91        match self {
92            ManagerError::Network(_) => idempotent,
93            ManagerError::Api { status, .. } => {
94                *status == 429 || (idempotent && matches!(status, 502..=504))
95            }
96            _ => false,
97        }
98    }
99}
100
101pub(crate) fn map_user_err<T>(e: crate::gen::user::apis::Error<T>) -> ManagerError {
102    use crate::gen::user::apis::Error;
103    match e {
104        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
105        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
106        Error::Serde(err) => ManagerError::Decode(err.to_string()),
107        Error::Io(err) => ManagerError::Network(err.to_string()),
108    }
109}
110
111impl<T> crate::retry::Transient for crate::gen::user::apis::Error<T> {
112    fn transient(&self, idempotent: bool) -> bool {
113        use crate::gen::user::apis::Error;
114        match self {
115            Error::Reqwest(_) | Error::Io(_) => idempotent,
116            Error::ResponseError(rc) => {
117                let s = rc.status.as_u16();
118                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
119            }
120            _ => false,
121        }
122    }
123}
124
125pub(crate) fn map_task_automation_err<T>(
126    e: crate::gen::task_automation::apis::Error<T>,
127) -> ManagerError {
128    use crate::gen::task_automation::apis::Error;
129    match e {
130        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
131        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
132        Error::Serde(err) => ManagerError::Decode(err.to_string()),
133        Error::Io(err) => ManagerError::Network(err.to_string()),
134    }
135}
136
137impl<T> crate::retry::Transient for crate::gen::task_automation::apis::Error<T> {
138    fn transient(&self, idempotent: bool) -> bool {
139        use crate::gen::task_automation::apis::Error;
140        match self {
141            Error::Reqwest(_) | Error::Io(_) => idempotent,
142            Error::ResponseError(rc) => {
143                let s = rc.status.as_u16();
144                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
145            }
146            _ => false,
147        }
148    }
149}
150
151pub(crate) fn map_task_schedule_err<T>(
152    e: crate::gen::task_schedule::apis::Error<T>,
153) -> ManagerError {
154    use crate::gen::task_schedule::apis::Error;
155    match e {
156        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
157        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
158        Error::Serde(err) => ManagerError::Decode(err.to_string()),
159        Error::Io(err) => ManagerError::Network(err.to_string()),
160    }
161}
162
163impl<T> crate::retry::Transient for crate::gen::task_schedule::apis::Error<T> {
164    fn transient(&self, idempotent: bool) -> bool {
165        use crate::gen::task_schedule::apis::Error;
166        match self {
167            Error::Reqwest(_) | Error::Io(_) => idempotent,
168            Error::ResponseError(rc) => {
169                let s = rc.status.as_u16();
170                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
171            }
172            _ => false,
173        }
174    }
175}
176
177pub(crate) fn map_auth_err<T>(e: crate::gen::auth::apis::Error<T>) -> ManagerError {
178    use crate::gen::auth::apis::Error;
179    match e {
180        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
181        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
182        Error::Serde(err) => ManagerError::Decode(err.to_string()),
183        Error::Io(err) => ManagerError::Network(err.to_string()),
184    }
185}
186
187impl<T> crate::retry::Transient for crate::gen::auth::apis::Error<T> {
188    fn transient(&self, idempotent: bool) -> bool {
189        use crate::gen::auth::apis::Error;
190        match self {
191            Error::Reqwest(_) | Error::Io(_) => idempotent,
192            Error::ResponseError(rc) => {
193                let s = rc.status.as_u16();
194                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
195            }
196            _ => false,
197        }
198    }
199}