Skip to main content

babelforce_manager_sdk/
client.rs

1use crate::auth::Auth;
2use crate::error::ManagerError;
3use crate::gen::auth::apis::configuration::Configuration as AuthConfig;
4use crate::gen::manager::apis::configuration::Configuration as ManagerConfig;
5use crate::gen::task_automation::apis::configuration::Configuration as TaskAutomationConfig;
6use crate::gen::task_schedule::apis::configuration::Configuration as TaskScheduleConfig;
7use crate::gen::user::apis::configuration::Configuration as UserConfig;
8use crate::resources::agents::{AgentGroupsResource, AgentsResource};
9use crate::resources::applications::{ApplicationsResource, LocalAutomationsResource};
10use crate::resources::auth::AuthResource;
11use crate::resources::automations::AutomationsResource;
12use crate::resources::babeldesk::{BabeldeskResource, BabeldeskWidgetsResource};
13use crate::resources::businesshours::BusinessHoursResource;
14use crate::resources::calendars::CalendarsResource;
15use crate::resources::calls::{CallsResource, ReportingResource};
16use crate::resources::campaigns::CampaignsResource;
17use crate::resources::conferences::ConferencesResource;
18use crate::resources::conversations::ConversationsResource;
19use crate::resources::dashboards::DashboardsResource;
20use crate::resources::dialer::{DialerBehavioursResource, DialerResource};
21use crate::resources::events::EventsResource;
22use crate::resources::expressions::ExpressionsResource;
23use crate::resources::files::FilesResource;
24use crate::resources::integrations::IntegrationsResource;
25use crate::resources::logs::LogsResource;
26use crate::resources::me::MeResource;
27use crate::resources::metrics::MetricsResource;
28use crate::resources::numbers::NumbersResource;
29use crate::resources::outbound::OutboundResource;
30use crate::resources::phonebook::PhonebookResource;
31use crate::resources::prompts::PromptsResource;
32use crate::resources::queues::{QueueSelectionsResource, QueuesResource};
33use crate::resources::recordings::RecordingsResource;
34use crate::resources::routing::RoutingResource;
35use crate::resources::schedules::TaskSchedulesResource;
36use crate::resources::sessions::SessionsResource;
37use crate::resources::settings::SettingsResource;
38use crate::resources::sms::SmsResource;
39use crate::resources::system::SystemResource;
40use crate::resources::tasks::{
41    TaskMetricsResource, TaskScriptsResource, TaskSecretsResource, TaskSelectionConfigResource,
42    TasksResource,
43};
44use crate::resources::triggers::TriggersResource;
45use crate::resources::users::UsersResource;
46use crate::retry::{RetryMiddleware, RetryPolicy};
47use crate::token::{SharedCfg, TokenManager};
48
49/// Default babelforce API host. Override with [`ManagerClientBuilder::base_url`].
50pub const DEFAULT_BASE_URL: &str = "https://services.babelforce.com";
51
52/// The babelforce manager SDK client. Build one with [`ManagerClient::connect`] (or
53/// [`ManagerClient::builder`]) and use its resource namespaces.
54pub struct ManagerClient {
55    /// User management — `/api/v2/users`.
56    pub users: UsersResource,
57    /// Agent management — `/api/v2/agents` (with a nested `groups` sub-resource).
58    pub agents: AgentsResource,
59    /// The authenticated principal — current user, accounts, password reset (`/api/v2/user`).
60    pub me: MeResource,
61    /// Call reporting & control — `/api/v2/calls` (with a nested `reporting` sub-resource).
62    pub calls: CallsResource,
63    /// SMS records — `/api/v2/sms`.
64    pub sms: SmsResource,
65    /// Service (phone) numbers — `/api/v2/numbers`.
66    pub numbers: NumbersResource,
67    /// Conferences — `/api/v2/conferences`.
68    pub conferences: ConferencesResource,
69    /// Queues — `/api/v2/queues` (with a nested `selections` sub-resource).
70    pub queues: QueuesResource,
71    /// Call recordings — `/api/v2/recordings` (bulk actions and per-recording flagging).
72    pub recordings: RecordingsResource,
73    /// Routing rules — `/api/v2/routings`.
74    pub routing: RoutingResource,
75    /// Workflow triggers — `/api/v2/triggers`.
76    pub triggers: TriggersResource,
77    /// Global automations (event triggers) — `/api/v2/events/triggers`.
78    pub automations: AutomationsResource,
79    /// Integrations — `/api/v2/integrations`.
80    pub integrations: IntegrationsResource,
81    /// Outbound lists & leads — `/api/v2/outbound/lists`.
82    pub outbound: OutboundResource,
83    /// Phonebook entries — `/api/v2/phonebook`.
84    pub phonebook: PhonebookResource,
85    /// Outbound campaigns — `/api/v2/outbound/campaigns`.
86    pub campaigns: CampaignsResource,
87    /// Business hours — `/api/v2/business-hours`.
88    pub business_hours: BusinessHoursResource,
89    /// Calendars — `/api/v2/calendars`.
90    pub calendars: CalendarsResource,
91    /// Conversations — `/api/v2/conversations` (with events & session variables).
92    pub conversations: ConversationsResource,
93    /// Reporting dashboards — `/api/v2/dashboards` (with per-dashboard user management).
94    pub dashboards: DashboardsResource,
95    /// Dialer runtime & reporting — `/api/v2/dialer` (with a nested `behaviours` sub-resource).
96    pub dialer: DialerResource,
97    /// Call/automation sessions — `/api/v2/sessions`.
98    pub sessions: SessionsResource,
99    /// Audio prompts — `/api/v2/prompts`.
100    pub prompts: PromptsResource,
101    /// Babeldesk dashboards — `/api/v2/babeldesk` (with a nested `widgets` sub-resource).
102    pub babeldesk: BabeldeskResource,
103    /// Event definitions & custom events — `/api/v2/events`.
104    pub events: EventsResource,
105    /// Expressions catalog & evaluator — `/api/v2/expressions`.
106    pub expressions: ExpressionsResource,
107    /// Stored files — `/api/v2/files` (recordings, prompts, backups).
108    pub files: FilesResource,
109    /// Audit & live logs.
110    pub logs: LogsResource,
111    /// Metrics — `/api/v2/metrics`.
112    pub metrics: MetricsResource,
113    /// System & reference data — health checks, server time, timezones, tags, templates.
114    pub system: SystemResource,
115    /// Global settings — `/api/v2/settings`, grouped by scope.
116    pub settings: SettingsResource,
117    /// Applications — `/api/v2/applications` (with a nested `local_automations` sub-resource).
118    pub applications: ApplicationsResource,
119    /// Task automation (v3) — `/api/v3/tasks` (with nested scripts/secrets/selection_config/metrics).
120    pub tasks: TasksResource,
121    /// Task schedules (v3) — `/api/v3/schedules`.
122    pub schedules: TaskSchedulesResource,
123    /// OAuth 2.0 — `/oauth/authorize`, `/oauth/token`, `/oauth/revoke`.
124    pub auth: AuthResource,
125}
126
127impl ManagerClient {
128    /// Connect with default settings (base URL [`DEFAULT_BASE_URL`], retries on).
129    pub async fn connect(auth: Auth) -> Result<Self, ManagerError> {
130        Self::builder(auth).connect().await
131    }
132
133    /// Start building a client to customise the base URL or retry policy.
134    pub fn builder(auth: Auth) -> ManagerClientBuilder {
135        ManagerClientBuilder {
136            base_url: DEFAULT_BASE_URL.to_string(),
137            auth,
138            retry: RetryPolicy::default(),
139        }
140    }
141}
142
143/// Builder for [`ManagerClient`].
144pub struct ManagerClientBuilder {
145    base_url: String,
146    auth: Auth,
147    retry: RetryPolicy,
148}
149
150impl ManagerClientBuilder {
151    /// Override the base URL (e.g. a per-customer host). Defaults to [`DEFAULT_BASE_URL`].
152    pub fn base_url(mut self, url: impl Into<String>) -> Self {
153        self.base_url = url.into();
154        self
155    }
156
157    /// Override the retry policy. Defaults to [`RetryPolicy::default`].
158    pub fn retry(mut self, policy: RetryPolicy) -> Self {
159        self.retry = policy;
160        self
161    }
162
163    /// Build the client, resolving a grant into a bearer token if needed (kept fresh from then
164    /// on — see [`Auth`]).
165    pub async fn connect(self) -> Result<ManagerClient, ManagerError> {
166        let base = self.base_url.trim_end_matches('/').to_string();
167
168        // Static headers only ([`Auth::Bearer`]). Grant-resolved tokens travel per-request via
169        // the configurations' `oauth_access_token`, so a mid-life re-grant never needs to
170        // rebuild this client.
171        let http = reqwest::Client::builder()
172            .default_headers(self.auth.static_headers())
173            .build()
174            .map_err(|e| ManagerError::Network(e.to_string()))?;
175        // Retries live in transport middleware (see `crate::retry`), so every request — generated
176        // clients, raw JSON paths and `/oauth/token` grants — shares one retry behaviour,
177        // matching the TS fetch wrapper / Go RoundTripper.
178        let http = reqwest_middleware::ClientBuilder::new(http)
179            .with(RetryMiddleware::new(self.retry.clone()))
180            .build();
181
182        // Resolve grant-based auth eagerly (fail fast at connect); the manager then re-grants
183        // transparently whenever a call would use the token within 30s of expiry.
184        let token_manager = TokenManager::connect(&base, &self.auth, http.clone()).await?;
185        let (epoch, token) = match &token_manager {
186            Some(manager) => {
187                let (epoch, token) = manager.current().await?;
188                (epoch, Some(token))
189            }
190            None => (0, None),
191        };
192
193        let user_agent =
194            Some(concat!("babelforce-manager-sdk-rust/", env!("CARGO_PKG_VERSION")).to_string());
195
196        let cfg = SharedCfg::new(
197            ManagerConfig {
198                base_path: base.clone(),
199                user_agent: user_agent.clone(),
200                client: http.clone(),
201                basic_auth: None,
202                oauth_access_token: token.clone(),
203                bearer_access_token: None,
204                api_key: None,
205            },
206            token_manager.clone(),
207            epoch,
208        );
209
210        // The other specs live on the same host and share the reqwest client + token lifecycle.
211        let user_cfg = SharedCfg::new(
212            UserConfig {
213                base_path: base.clone(),
214                user_agent: user_agent.clone(),
215                client: http.clone(),
216                basic_auth: None,
217                oauth_access_token: token.clone(),
218                bearer_access_token: None,
219                api_key: None,
220            },
221            token_manager.clone(),
222            epoch,
223        );
224        let task_automation_cfg = SharedCfg::new(
225            TaskAutomationConfig {
226                base_path: base.clone(),
227                user_agent: user_agent.clone(),
228                client: http.clone(),
229                basic_auth: None,
230                oauth_access_token: token.clone(),
231                bearer_access_token: None,
232                api_key: None,
233            },
234            token_manager.clone(),
235            epoch,
236        );
237        let task_schedule_cfg = SharedCfg::new(
238            TaskScheduleConfig {
239                base_path: base.clone(),
240                user_agent: user_agent.clone(),
241                client: http.clone(),
242                basic_auth: None,
243                oauth_access_token: token.clone(),
244                bearer_access_token: None,
245                api_key: None,
246            },
247            token_manager.clone(),
248            epoch,
249        );
250        // OAuth endpoints carry their credentials in the request body, but share the same
251        // host/client.
252        let auth_cfg = SharedCfg::new(
253            AuthConfig {
254                base_path: base,
255                user_agent,
256                client: http,
257                basic_auth: None,
258                oauth_access_token: token,
259                bearer_access_token: None,
260                api_key: None,
261            },
262            token_manager,
263            epoch,
264        );
265
266        Ok(ManagerClient {
267            users: UsersResource {
268                cfg: cfg.clone(),
269                retry: self.retry.clone(),
270            },
271            agents: AgentsResource {
272                cfg: cfg.clone(),
273                retry: self.retry.clone(),
274                groups: AgentGroupsResource {
275                    cfg: cfg.clone(),
276                    retry: self.retry.clone(),
277                },
278            },
279            me: MeResource {
280                cfg: user_cfg,
281                retry: self.retry.clone(),
282            },
283            calls: CallsResource {
284                cfg: cfg.clone(),
285                retry: self.retry.clone(),
286                reporting: ReportingResource {
287                    cfg: cfg.clone(),
288                    retry: self.retry.clone(),
289                },
290            },
291            sms: SmsResource {
292                cfg: cfg.clone(),
293                retry: self.retry.clone(),
294            },
295            numbers: NumbersResource {
296                cfg: cfg.clone(),
297                retry: self.retry.clone(),
298            },
299            conferences: ConferencesResource {
300                cfg: cfg.clone(),
301                retry: self.retry.clone(),
302            },
303            queues: QueuesResource {
304                cfg: cfg.clone(),
305                retry: self.retry.clone(),
306                selections: QueueSelectionsResource {
307                    cfg: cfg.clone(),
308                    retry: self.retry.clone(),
309                },
310            },
311            recordings: RecordingsResource {
312                cfg: cfg.clone(),
313                retry: self.retry.clone(),
314            },
315            routing: RoutingResource {
316                cfg: cfg.clone(),
317                retry: self.retry.clone(),
318            },
319            triggers: TriggersResource {
320                cfg: cfg.clone(),
321                retry: self.retry.clone(),
322            },
323            automations: AutomationsResource {
324                cfg: cfg.clone(),
325                retry: self.retry.clone(),
326            },
327            integrations: IntegrationsResource {
328                cfg: cfg.clone(),
329                retry: self.retry.clone(),
330            },
331            outbound: OutboundResource {
332                cfg: cfg.clone(),
333                retry: self.retry.clone(),
334            },
335            phonebook: PhonebookResource {
336                cfg: cfg.clone(),
337                retry: self.retry.clone(),
338            },
339            campaigns: CampaignsResource {
340                cfg: cfg.clone(),
341                retry: self.retry.clone(),
342            },
343            business_hours: BusinessHoursResource {
344                cfg: cfg.clone(),
345                retry: self.retry.clone(),
346            },
347            calendars: CalendarsResource {
348                cfg: cfg.clone(),
349                retry: self.retry.clone(),
350            },
351            conversations: ConversationsResource {
352                cfg: cfg.clone(),
353                retry: self.retry.clone(),
354            },
355            dashboards: DashboardsResource {
356                cfg: cfg.clone(),
357                retry: self.retry.clone(),
358            },
359            dialer: DialerResource {
360                cfg: cfg.clone(),
361                retry: self.retry.clone(),
362                behaviours: DialerBehavioursResource {
363                    cfg: cfg.clone(),
364                    retry: self.retry.clone(),
365                },
366            },
367            sessions: SessionsResource {
368                cfg: cfg.clone(),
369                retry: self.retry.clone(),
370            },
371            prompts: PromptsResource {
372                cfg: cfg.clone(),
373                retry: self.retry.clone(),
374            },
375            babeldesk: BabeldeskResource {
376                cfg: cfg.clone(),
377                retry: self.retry.clone(),
378                widgets: BabeldeskWidgetsResource {
379                    cfg: cfg.clone(),
380                    retry: self.retry.clone(),
381                },
382            },
383            events: EventsResource {
384                cfg: cfg.clone(),
385                retry: self.retry.clone(),
386            },
387            expressions: ExpressionsResource {
388                cfg: cfg.clone(),
389                retry: self.retry.clone(),
390            },
391            files: FilesResource {
392                cfg: cfg.clone(),
393                retry: self.retry.clone(),
394            },
395            logs: LogsResource {
396                cfg: cfg.clone(),
397                retry: self.retry.clone(),
398            },
399            metrics: MetricsResource {
400                cfg: cfg.clone(),
401                retry: self.retry.clone(),
402            },
403            system: SystemResource {
404                cfg: cfg.clone(),
405                retry: self.retry.clone(),
406            },
407            settings: SettingsResource {
408                cfg: cfg.clone(),
409                retry: self.retry.clone(),
410            },
411            applications: ApplicationsResource {
412                cfg: cfg.clone(),
413                retry: self.retry.clone(),
414                local_automations: LocalAutomationsResource {
415                    cfg: cfg.clone(),
416                    retry: self.retry.clone(),
417                },
418            },
419            tasks: TasksResource {
420                cfg: task_automation_cfg.clone(),
421                retry: self.retry.clone(),
422                scripts: TaskScriptsResource {
423                    cfg: task_automation_cfg.clone(),
424                    retry: self.retry.clone(),
425                },
426                secrets: TaskSecretsResource {
427                    cfg: task_automation_cfg.clone(),
428                    retry: self.retry.clone(),
429                },
430                selection_config: TaskSelectionConfigResource {
431                    cfg: task_automation_cfg.clone(),
432                    retry: self.retry.clone(),
433                },
434                metrics: TaskMetricsResource {
435                    cfg: task_automation_cfg.clone(),
436                    retry: self.retry.clone(),
437                },
438            },
439            schedules: TaskSchedulesResource {
440                cfg: task_schedule_cfg,
441                retry: self.retry.clone(),
442            },
443            auth: AuthResource {
444                cfg: auth_cfg,
445                retry: self.retry.clone(),
446            },
447        })
448    }
449}