Skip to main content

babelforce_manager_sdk/resources/
applications.rs

1use std::sync::Arc;
2
3use serde_json::Value;
4
5use crate::error::{map_manager_err, ManagerError};
6use crate::gen::manager::apis::configuration::Configuration;
7use crate::gen::manager::apis::{application_api as api, local_automation_api, manager_api};
8use crate::gen::manager::models;
9use crate::http::{collect_all, fetch_page, Page};
10use crate::resources::raw::{get_json, post_json, post_json_body};
11use crate::retry::{with_retry, RetryPolicy};
12
13/// Decode one application object **variant-directly**.
14///
15/// The generated [`models::Application`] is an internally-tagged enum (`#[serde(tag = "module")]`),
16/// so serde strips the tag before deserializing the variant content — but every generated variant
17/// struct *requires* its own `module` field, making the derived `Deserialize` fail on **every**
18/// real payload (an openapi-generator discriminator artifact). The facade reads the tag itself and
19/// deserializes the full (tag-carrying) object straight into the variant struct — no generated
20/// code touched.
21fn decode_application(v: Value) -> Result<models::Application, ManagerError> {
22    use models::Application as App;
23    fn var<T: serde::de::DeserializeOwned>(v: Value) -> Result<Box<T>, ManagerError> {
24        serde_json::from_value(v)
25            .map(Box::new)
26            .map_err(|e| ManagerError::Decode(e.to_string()))
27    }
28    let module = v
29        .get("module")
30        .and_then(Value::as_str)
31        .unwrap_or_default()
32        .to_string();
33    Ok(match module.as_str() {
34        "agentQueue" => App::AgentQueue(var(v)?),
35        "agentic" => App::Agentic(var(v)?),
36        "audioPlayer" => App::AudioPlayer(var(v)?),
37        "consumerQueue" => App::ConsumerQueue(var(v)?),
38        "inputReader" => App::InputReader(var(v)?),
39        "inputReader.v2" => App::InputReaderV2(var(v)?),
40        "promptPlayer" => App::PromptPlayer(var(v)?),
41        "recording" => App::Recording(var(v)?),
42        "simpleMenu" => App::SimpleMenu(var(v)?),
43        "speechToText" => App::SpeechToText(var(v)?),
44        "switchNode" => App::SwitchNode(var(v)?),
45        "textToSpeech" => App::TextToSpeech(var(v)?),
46        "transfer" => App::Transfer(var(v)?),
47        other => {
48            return Err(ManagerError::Decode(format!(
49                "unknown application module '{other}'"
50            )))
51        }
52    })
53}
54
55/// One raw page of `/api/v2/applications`, decoded item-by-item.
56///
57/// Stands in for the generated `list_applications`: that returns the internally-tagged
58/// [`models::Application`] enum, which fails to deserialize on every real payload (see
59/// [`decode_application`]), so the facade reads raw and decodes each item itself.
60async fn applications_page(
61    cfg: &Configuration,
62    page: i32,
63    per_page: Option<i32>,
64) -> Result<(Vec<models::Application>, i32, i32, Option<i32>), ManagerError> {
65    let mut path = format!("/api/v2/applications?page={page}");
66    if let Some(max) = per_page {
67        path.push_str(&format!("&max={max}"));
68    }
69    let v = get_json(cfg, &path).await?;
70    let items = v
71        .get("items")
72        .and_then(Value::as_array)
73        .cloned()
74        .unwrap_or_default()
75        .into_iter()
76        .map(decode_application)
77        .collect::<Result<Vec<_>, _>>()?;
78    let p = v.get("pagination").cloned().unwrap_or(Value::Null);
79    let num = |k: &str| p.get(k).and_then(Value::as_i64).map(|n| n as i32);
80    Ok((
81        items,
82        num("pages").unwrap_or(1),
83        num("current").unwrap_or(page),
84        num("total"),
85    ))
86}
87
88/// Applications — `/api/v2/applications`, with a nested `local_automations` sub-resource.
89pub struct ApplicationsResource {
90    pub(crate) cfg: Arc<Configuration>,
91    pub(crate) retry: RetryPolicy,
92    /// Per-application local automations — `/api/v2/applications/{applicationId}/actions`.
93    pub local_automations: LocalAutomationsResource,
94}
95
96impl ApplicationsResource {
97    /// List all applications (auto-paginated).
98    pub async fn list_all(&self) -> Result<Vec<models::Application>, ManagerError> {
99        collect_all(
100            &self.retry,
101            |e| e,
102            |page| async move {
103                applications_page(self.cfg.as_ref(), page, None)
104                    .await
105                    .map(|(items, pages, current, _)| (items, pages, current))
106            },
107        )
108        .await
109    }
110
111    /// List one page of applications.
112    pub async fn list_page(
113        &self,
114        page: i32,
115        per_page: Option<i32>,
116    ) -> Result<Page<models::Application>, ManagerError> {
117        fetch_page(
118            &self.retry,
119            |e| e,
120            || async move { applications_page(self.cfg.as_ref(), page, per_page).await },
121        )
122        .await
123    }
124
125    /// List all local automations across applications (auto-paginated).
126    pub async fn all_local_automations(
127        &self,
128    ) -> Result<Vec<models::LocalAutomation>, ManagerError> {
129        collect_all(&self.retry, map_manager_err, |page| async move {
130            let r = local_automation_api::list_all_local_automations(
131                self.cfg.as_ref(),
132                Some(page),
133                None,
134            )
135            .await?;
136            Ok((r.items, r.pagination.pages, r.pagination.current))
137        })
138        .await
139    }
140
141    /// Create an application.
142    pub async fn create(
143        &self,
144        body: models::ApplicationCreateBody,
145    ) -> Result<models::ApplicationItemResponse, ManagerError> {
146        with_retry(&self.retry, false, || {
147            api::create_application(self.cfg.as_ref(), body.clone())
148        })
149        .await
150        .map_err(map_manager_err)
151    }
152
153    /// Create an application, reading the response **raw**.
154    ///
155    /// The typed [`Self::create`] returns [`models::ApplicationItemResponse`], whose derived
156    /// `Application` decode fails on every real success payload (the `module` discriminator
157    /// artifact — see [`decode_application`]) — misreporting a *completed* create as an error.
158    /// Callers that need the created record read raw and lose nothing.
159    pub async fn create_raw(&self, body: Value) -> Result<Value, ManagerError> {
160        // Non-idempotent create → no retry on ambiguous failures.
161        with_retry(&self.retry, false, || {
162            post_json_body(self.cfg.as_ref(), "/api/v2/applications", &body)
163        })
164        .await
165    }
166
167    /// Get an application by id.
168    ///
169    /// Reads the response **raw** and decodes via [`decode_application`], standing in for the
170    /// generated `get_application` — whose typed [`models::Application`] return fails to
171    /// deserialize on every real payload.
172    pub async fn get(&self, id: &str) -> Result<models::ApplicationItemResponse, ManagerError> {
173        let path = format!(
174            "/api/v2/applications/{}",
175            crate::gen::manager::apis::urlencode(id)
176        );
177        with_retry(&self.retry, true, || async {
178            let v = get_json(self.cfg.as_ref(), &path).await?;
179            let success = v.get("success").and_then(Value::as_bool).unwrap_or(true);
180            let item = v
181                .get("item")
182                .cloned()
183                .ok_or_else(|| ManagerError::Decode("response had no `item`".into()))?;
184            Ok(models::ApplicationItemResponse::new(
185                decode_application(item)?,
186                success,
187            ))
188        })
189        .await
190    }
191
192    /// Update an application.
193    pub async fn update(
194        &self,
195        id: &str,
196        body: models::ApplicationUpdateBody,
197    ) -> Result<models::ApplicationItemResponse, ManagerError> {
198        with_retry(&self.retry, false, || {
199            api::update_application(self.cfg.as_ref(), id, body.clone())
200        })
201        .await
202        .map_err(map_manager_err)
203    }
204
205    /// Delete an application.
206    pub async fn delete(&self, id: &str) -> Result<(), ManagerError> {
207        with_retry(&self.retry, false, || {
208            api::delete_application(self.cfg.as_ref(), id)
209        })
210        .await
211        .map_err(map_manager_err)?;
212        Ok(())
213    }
214
215    /// Delete several applications by id.
216    pub async fn delete_many(
217        &self,
218        ids: Vec<String>,
219    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
220        let body = models::DeleteManyApplicationsRequest { ids };
221        with_retry(&self.retry, false, || {
222            api::delete_many_applications(self.cfg.as_ref(), body.clone())
223        })
224        .await
225        .map_err(map_manager_err)
226    }
227
228    /// List the available application modules.
229    pub async fn list_modules(&self) -> Result<models::ListModulesResponse, ManagerError> {
230        with_retry(&self.retry, true, || api::list_modules(self.cfg.as_ref()))
231            .await
232            .map_err(map_manager_err)
233    }
234
235    /// Dispatch an application's local automations at a given position. `is_async` runs without
236    /// waiting for the result.
237    pub async fn dispatch(
238        &self,
239        id: &str,
240        position: &str,
241        is_async: bool,
242        body: models::LocalAutomationDispatch,
243    ) -> Result<models::DispatchLocalAutomationsResponse, ManagerError> {
244        with_retry(&self.retry, false, || {
245            manager_api::dispatch_local_automations(
246                self.cfg.as_ref(),
247                id,
248                position,
249                is_async,
250                Some(body.clone()),
251            )
252        })
253        .await
254        .map_err(map_manager_err)
255    }
256
257    /// Clone an application by id.
258    pub async fn clone(&self, id: &str) -> Result<models::ApplicationItemResponse, ManagerError> {
259        with_retry(&self.retry, false, || {
260            api::clone_application(self.cfg.as_ref(), id)
261        })
262        .await
263        .map_err(map_manager_err)
264    }
265
266    /// Clone an application by id, reading the response **raw** (same decode caveat as
267    /// [`Self::create_raw`]: the typed [`Self::clone`] fails on populated success bodies).
268    pub async fn clone_raw(&self, id: &str) -> Result<Value, ManagerError> {
269        let path = format!(
270            "/api/v2/applications/{}/clone",
271            crate::gen::manager::apis::urlencode(id)
272        );
273        // Non-idempotent action → no retry on ambiguous failures.
274        with_retry(&self.retry, false, || post_json(self.cfg.as_ref(), &path)).await
275    }
276
277    /// Bulk-update several applications in one request.
278    pub async fn bulk_update(
279        &self,
280        body: models::BulkUpdateApplicationsRequest,
281    ) -> Result<models::DefaultV2MessageResponse, ManagerError> {
282        with_retry(&self.retry, false, || {
283            api::bulk_update_applications(self.cfg.as_ref(), body.clone())
284        })
285        .await
286        .map_err(map_manager_err)
287    }
288
289    /// List the actions available across applications.
290    pub async fn list_app_actions(&self) -> Result<models::ObjectListResponse, ManagerError> {
291        with_retry(&self.retry, true, || {
292            api::list_application_actions(self.cfg.as_ref(), None, None)
293        })
294        .await
295        .map_err(map_manager_err)
296    }
297
298    /// List application errors.
299    pub async fn list_errors(&self) -> Result<models::ObjectListResponse, ManagerError> {
300        with_retry(&self.retry, true, || {
301            api::list_application_errors(self.cfg.as_ref())
302        })
303        .await
304        .map_err(map_manager_err)
305    }
306}
307
308/// Per-application local automations — `/api/v2/applications/{applicationId}/actions`.
309pub struct LocalAutomationsResource {
310    pub(crate) cfg: Arc<Configuration>,
311    pub(crate) retry: RetryPolicy,
312}
313
314impl LocalAutomationsResource {
315    /// List all of an application's local automations (auto-paginated).
316    pub async fn list_all(
317        &self,
318        application_id: &str,
319    ) -> Result<Vec<models::LocalAutomation>, ManagerError> {
320        collect_all(&self.retry, map_manager_err, |page| async move {
321            let r = local_automation_api::list_local_automations(
322                self.cfg.as_ref(),
323                application_id,
324                Some(page),
325                None,
326            )
327            .await?;
328            Ok((r.items, r.pagination.pages, r.pagination.current))
329        })
330        .await
331    }
332
333    /// Create a local automation on an application.
334    pub async fn create(
335        &self,
336        application_id: &str,
337        body: models::RestCreateLocalAutomation,
338    ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
339        with_retry(&self.retry, false, || {
340            local_automation_api::create_local_automation(
341                self.cfg.as_ref(),
342                application_id,
343                body.clone(),
344            )
345        })
346        .await
347        .map_err(map_manager_err)
348    }
349
350    /// Get a local automation by id.
351    pub async fn get(
352        &self,
353        application_id: &str,
354        id: &str,
355    ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
356        with_retry(&self.retry, true, || {
357            local_automation_api::get_local_automation(self.cfg.as_ref(), application_id, id)
358        })
359        .await
360        .map_err(map_manager_err)
361    }
362
363    /// Update a local automation.
364    pub async fn update(
365        &self,
366        application_id: &str,
367        id: &str,
368        body: models::RestUpdateLocalAutomation,
369    ) -> Result<models::LocalAutomationItemResponse, ManagerError> {
370        with_retry(&self.retry, false, || {
371            local_automation_api::update_local_automation(
372                self.cfg.as_ref(),
373                application_id,
374                id,
375                body.clone(),
376            )
377        })
378        .await
379        .map_err(map_manager_err)
380    }
381
382    /// Delete a local automation.
383    pub async fn delete(&self, application_id: &str, id: &str) -> Result<(), ManagerError> {
384        with_retry(&self.retry, false, || {
385            local_automation_api::delete_local_automation(self.cfg.as_ref(), application_id, id)
386        })
387        .await
388        .map_err(map_manager_err)?;
389        Ok(())
390    }
391}