files_sdk/automation/
automations.rs

1//! Automation operations
2//!
3//! Automations allow you to automate file operations like copying, moving, deleting files,
4//! or running syncs on a schedule or triggered by file events.
5
6use crate::{FilesClient, PaginationInfo, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10/// Automation type enum
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum AutomationType {
14    CreateFolder,
15    DeleteFile,
16    CopyFile,
17    MoveFile,
18    As2Send,
19    RunSync,
20    ImportFile,
21}
22
23/// Automation trigger type
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum AutomationTrigger {
27    Daily,
28    Custom,
29    Webhook,
30    Email,
31    Action,
32    Interval,
33}
34
35/// An Automation entity
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct AutomationEntity {
38    /// Automation ID
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub id: Option<i64>,
41
42    /// Force automation runs to be serialized
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub always_serialize_jobs: Option<bool>,
45
46    /// Always overwrite files with matching size
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub always_overwrite_size_matching_files: Option<bool>,
49
50    /// Automation type
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub automation: Option<String>,
53
54    /// Indicates if the automation has been deleted
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub deleted: Option<bool>,
57
58    /// Description for this Automation
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub description: Option<String>,
61
62    /// String to replace in destination path
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub destination_replace_from: Option<String>,
65
66    /// Replacement string for destination path
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub destination_replace_to: Option<String>,
69
70    /// Destination paths
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub destinations: Option<Vec<String>>,
73
74    /// If true, this automation will not run
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub disabled: Option<bool>,
77
78    /// Glob pattern to exclude files
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub exclude_pattern: Option<String>,
81
82    /// Flatten destination folder structure
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub flatten_destination_structure: Option<bool>,
85
86    /// Group IDs associated with automation
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub group_ids: Option<Vec<i64>>,
89
90    /// Holiday region for scheduling
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub holiday_region: Option<String>,
93
94    /// Human readable schedule description
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub human_readable_schedule: Option<String>,
97
98    /// Ignore locked folders
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub ignore_locked_folders: Option<bool>,
101
102    /// URLs to import from
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub import_urls: Option<Vec<serde_json::Value>>,
105
106    /// Automation interval (day, week, month, etc.)
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub interval: Option<String>,
109
110    /// Last modification time
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub last_modified_at: Option<String>,
113
114    /// Use legacy folder matching
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub legacy_folder_matching: Option<bool>,
117
118    /// Legacy sync IDs
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub legacy_sync_ids: Option<Vec<i64>>,
121
122    /// Automation name
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub name: Option<String>,
125
126    /// Overwrite existing files
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub overwrite_files: Option<bool>,
129
130    /// Path on which this Automation runs
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub path: Option<String>,
133
134    /// Path timezone
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub path_time_zone: Option<String>,
137
138    /// Recurring day of interval
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub recurring_day: Option<i64>,
141
142    /// Retry interval on failure (minutes)
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub retry_on_failure_interval_in_minutes: Option<i64>,
145
146    /// Number of retry attempts on failure
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub retry_on_failure_number_of_attempts: Option<i64>,
149
150    /// Custom schedule configuration
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub schedule: Option<serde_json::Value>,
153
154    /// Days of week for schedule
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub schedule_days_of_week: Option<Vec<i64>>,
157
158    /// Schedule timezone
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub schedule_time_zone: Option<String>,
161
162    /// Times of day for schedule
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub schedule_times_of_day: Option<Vec<String>>,
165
166    /// Source path/glob
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub source: Option<String>,
169
170    /// Sync IDs
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub sync_ids: Option<Vec<i64>>,
173
174    /// Trigger type
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub trigger: Option<String>,
177
178    /// Actions that trigger this automation
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub trigger_actions: Option<Vec<String>>,
181
182    /// User ID that owns this automation
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub user_id: Option<i64>,
185
186    /// User IDs associated with automation
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub user_ids: Option<Vec<i64>>,
189
190    /// Automation value/configuration
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub value: Option<serde_json::Value>,
193
194    /// Webhook URL
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub webhook_url: Option<String>,
197}
198
199/// Handler for automation operations
200pub struct AutomationHandler {
201    client: FilesClient,
202}
203
204impl AutomationHandler {
205    /// Create a new automation handler
206    pub fn new(client: FilesClient) -> Self {
207        Self { client }
208    }
209
210    /// List automations
211    ///
212    /// # Arguments
213    /// * `cursor` - Pagination cursor
214    /// * `per_page` - Results per page
215    /// * `automation` - Filter by automation type
216    ///
217    /// # Returns
218    /// Tuple of (automations, pagination_info)
219    ///
220    /// # Example
221    /// ```no_run
222    /// use files_sdk::{FilesClient, AutomationHandler};
223    ///
224    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
225    /// let client = FilesClient::builder().api_key("key").build()?;
226    /// let handler = AutomationHandler::new(client);
227    /// let (automations, _) = handler.list(None, None, None).await?;
228    /// # Ok(())
229    /// # }
230    /// ```
231    pub async fn list(
232        &self,
233        cursor: Option<&str>,
234        per_page: Option<i64>,
235        automation: Option<&str>,
236    ) -> Result<(Vec<AutomationEntity>, PaginationInfo)> {
237        let mut params = vec![];
238        if let Some(c) = cursor {
239            params.push(("cursor", c.to_string()));
240        }
241        if let Some(pp) = per_page {
242            params.push(("per_page", pp.to_string()));
243        }
244        if let Some(a) = automation {
245            params.push(("automation", a.to_string()));
246        }
247
248        let query = if params.is_empty() {
249            String::new()
250        } else {
251            format!(
252                "?{}",
253                params
254                    .iter()
255                    .map(|(k, v)| format!("{}={}", k, v))
256                    .collect::<Vec<_>>()
257                    .join("&")
258            )
259        };
260
261        let response = self
262            .client
263            .get_raw(&format!("/automations{}", query))
264            .await?;
265        let automations: Vec<AutomationEntity> = serde_json::from_value(response)?;
266
267        let pagination = PaginationInfo {
268            cursor_next: None,
269            cursor_prev: None,
270        };
271
272        Ok((automations, pagination))
273    }
274
275    /// Get a specific automation
276    ///
277    /// # Arguments
278    /// * `id` - Automation ID
279    pub async fn get(&self, id: i64) -> Result<AutomationEntity> {
280        let response = self.client.get_raw(&format!("/automations/{}", id)).await?;
281        Ok(serde_json::from_value(response)?)
282    }
283
284    /// Create a new automation
285    ///
286    /// # Arguments
287    /// * `automation` - Automation type (required)
288    /// * `source` - Source path/glob
289    /// * `destination` - Destination path
290    /// * `destinations` - Destination paths (array)
291    /// * `interval` - Schedule interval
292    /// * `path` - Path on which automation runs
293    /// * `trigger` - Trigger type
294    ///
295    /// # Returns
296    /// The created automation
297    ///
298    /// # Example
299    /// ```no_run
300    /// use files_sdk::{FilesClient, AutomationHandler};
301    ///
302    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
303    /// let client = FilesClient::builder().api_key("key").build()?;
304    /// let handler = AutomationHandler::new(client);
305    /// let automation = handler.create(
306    ///     "copy_file",
307    ///     Some("/source/*.txt"),
308    ///     Some("/destination/"),
309    ///     None,
310    ///     None,
311    ///     Some("/source"),
312    ///     Some("action")
313    /// ).await?;
314    /// # Ok(())
315    /// # }
316    /// ```
317    #[allow(clippy::too_many_arguments)]
318    pub async fn create(
319        &self,
320        automation: &str,
321        source: Option<&str>,
322        destination: Option<&str>,
323        destinations: Option<Vec<String>>,
324        interval: Option<&str>,
325        path: Option<&str>,
326        trigger: Option<&str>,
327    ) -> Result<AutomationEntity> {
328        let mut request_body = json!({
329            "automation": automation,
330        });
331
332        if let Some(s) = source {
333            request_body["source"] = json!(s);
334        }
335        if let Some(d) = destination {
336            request_body["destination"] = json!(d);
337        }
338        if let Some(dests) = destinations {
339            request_body["destinations"] = json!(dests);
340        }
341        if let Some(i) = interval {
342            request_body["interval"] = json!(i);
343        }
344        if let Some(p) = path {
345            request_body["path"] = json!(p);
346        }
347        if let Some(t) = trigger {
348            request_body["trigger"] = json!(t);
349        }
350
351        let response = self.client.post_raw("/automations", request_body).await?;
352        Ok(serde_json::from_value(response)?)
353    }
354
355    /// Update an automation
356    ///
357    /// # Arguments
358    /// * `id` - Automation ID
359    /// * `source` - Source path/glob
360    /// * `destination` - Destination path
361    /// * `interval` - Schedule interval
362    /// * `disabled` - Disable the automation
363    ///
364    /// # Returns
365    /// The updated automation
366    #[allow(clippy::too_many_arguments)]
367    pub async fn update(
368        &self,
369        id: i64,
370        source: Option<&str>,
371        destination: Option<&str>,
372        interval: Option<&str>,
373        disabled: Option<bool>,
374    ) -> Result<AutomationEntity> {
375        let mut request_body = json!({});
376
377        if let Some(s) = source {
378            request_body["source"] = json!(s);
379        }
380        if let Some(d) = destination {
381            request_body["destination"] = json!(d);
382        }
383        if let Some(i) = interval {
384            request_body["interval"] = json!(i);
385        }
386        if let Some(dis) = disabled {
387            request_body["disabled"] = json!(dis);
388        }
389
390        let response = self
391            .client
392            .patch_raw(&format!("/automations/{}", id), request_body)
393            .await?;
394        Ok(serde_json::from_value(response)?)
395    }
396
397    /// Delete an automation
398    ///
399    /// # Arguments
400    /// * `id` - Automation ID
401    pub async fn delete(&self, id: i64) -> Result<()> {
402        self.client
403            .delete_raw(&format!("/automations/{}", id))
404            .await?;
405        Ok(())
406    }
407
408    /// Manually run an automation
409    ///
410    /// # Arguments
411    /// * `id` - Automation ID
412    ///
413    /// # Returns
414    /// The automation run result
415    pub async fn manual_run(&self, id: i64) -> Result<serde_json::Value> {
416        let response = self
417            .client
418            .post_raw(&format!("/automations/{}/manual_run", id), json!({}))
419            .await?;
420        Ok(response)
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use super::*;
427
428    #[test]
429    fn test_handler_creation() {
430        let client = FilesClient::builder().api_key("test-key").build().unwrap();
431        let _handler = AutomationHandler::new(client);
432    }
433}