files_sdk/automation/
behaviors.rs

1//! Behavior operations
2//!
3//! Behaviors are folder-level settings that automate actions like webhooks,
4//! file expiration, encryption, and more.
5
6use crate::{FilesClient, PaginationInfo, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10/// A Behavior entity
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct BehaviorEntity {
13    /// Behavior ID
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub id: Option<i64>,
16
17    /// Folder path where behavior applies
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub path: Option<String>,
20
21    /// URL for attached file (if applicable)
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub attachment_url: Option<String>,
24
25    /// Behavior type (e.g., webhook, auto_encrypt, file_expiration)
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub behavior: Option<String>,
28
29    /// Description of this behavior
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub description: Option<String>,
32
33    /// Name of the behavior
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub name: Option<String>,
36
37    /// Behavior configuration value (hash/object)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub value: Option<serde_json::Value>,
40
41    /// Disable parent folder behavior
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub disable_parent_folder_behavior: Option<bool>,
44
45    /// Apply recursively to subfolders
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub recursive: Option<bool>,
48}
49
50/// Handler for behavior operations
51pub struct BehaviorHandler {
52    client: FilesClient,
53}
54
55impl BehaviorHandler {
56    /// Create a new behavior handler
57    pub fn new(client: FilesClient) -> Self {
58        Self { client }
59    }
60
61    /// List behaviors
62    ///
63    /// # Arguments
64    /// * `cursor` - Pagination cursor
65    /// * `per_page` - Results per page
66    /// * `sort_by` - Sort field and direction
67    /// * `filter` - Filter criteria
68    /// * `filter_prefix` - Filter by path prefix
69    ///
70    /// # Returns
71    /// Tuple of (behaviors, pagination_info)
72    ///
73    /// # Example
74    /// ```no_run
75    /// use files_sdk::{FilesClient, BehaviorHandler};
76    ///
77    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
78    /// let client = FilesClient::builder().api_key("key").build()?;
79    /// let handler = BehaviorHandler::new(client);
80    /// let (behaviors, _) = handler.list(None, None, None, None, None).await?;
81    /// # Ok(())
82    /// # }
83    /// ```
84    #[allow(clippy::too_many_arguments)]
85    pub async fn list(
86        &self,
87        cursor: Option<&str>,
88        per_page: Option<i64>,
89        sort_by: Option<serde_json::Value>,
90        filter: Option<serde_json::Value>,
91        filter_prefix: Option<&str>,
92    ) -> Result<(Vec<BehaviorEntity>, PaginationInfo)> {
93        let mut params = vec![];
94        if let Some(c) = cursor {
95            params.push(("cursor", c.to_string()));
96        }
97        if let Some(pp) = per_page {
98            params.push(("per_page", pp.to_string()));
99        }
100        if let Some(sb) = sort_by {
101            params.push(("sort_by", sb.to_string()));
102        }
103        if let Some(f) = filter {
104            params.push(("filter", f.to_string()));
105        }
106        if let Some(fp) = filter_prefix {
107            params.push(("filter_prefix", fp.to_string()));
108        }
109
110        let query = if params.is_empty() {
111            String::new()
112        } else {
113            format!(
114                "?{}",
115                params
116                    .iter()
117                    .map(|(k, v)| format!("{}={}", k, v))
118                    .collect::<Vec<_>>()
119                    .join("&")
120            )
121        };
122
123        let response = self.client.get_raw(&format!("/behaviors{}", query)).await?;
124        let behaviors: Vec<BehaviorEntity> = serde_json::from_value(response)?;
125
126        let pagination = PaginationInfo {
127            cursor_next: None,
128            cursor_prev: None,
129        };
130
131        Ok((behaviors, pagination))
132    }
133
134    /// List behaviors for a specific folder path
135    ///
136    /// # Arguments
137    /// * `path` - Folder path
138    /// * `cursor` - Pagination cursor
139    /// * `per_page` - Results per page
140    ///
141    /// # Returns
142    /// Tuple of (behaviors, pagination_info)
143    pub async fn list_for_folder(
144        &self,
145        path: &str,
146        cursor: Option<&str>,
147        per_page: Option<i64>,
148    ) -> Result<(Vec<BehaviorEntity>, PaginationInfo)> {
149        let mut params = vec![];
150        if let Some(c) = cursor {
151            params.push(("cursor", c.to_string()));
152        }
153        if let Some(pp) = per_page {
154            params.push(("per_page", pp.to_string()));
155        }
156
157        let query = if params.is_empty() {
158            String::new()
159        } else {
160            format!(
161                "?{}",
162                params
163                    .iter()
164                    .map(|(k, v)| format!("{}={}", k, v))
165                    .collect::<Vec<_>>()
166                    .join("&")
167            )
168        };
169
170        let response = self
171            .client
172            .get_raw(&format!("/behaviors/folders/{}{}", path, query))
173            .await?;
174        let behaviors: Vec<BehaviorEntity> = serde_json::from_value(response)?;
175
176        let pagination = PaginationInfo {
177            cursor_next: None,
178            cursor_prev: None,
179        };
180
181        Ok((behaviors, pagination))
182    }
183
184    /// Get a specific behavior
185    ///
186    /// # Arguments
187    /// * `id` - Behavior ID
188    pub async fn get(&self, id: i64) -> Result<BehaviorEntity> {
189        let response = self.client.get_raw(&format!("/behaviors/{}", id)).await?;
190        Ok(serde_json::from_value(response)?)
191    }
192
193    /// Create a new behavior
194    ///
195    /// # Arguments
196    /// * `path` - Folder path (required)
197    /// * `behavior` - Behavior type (required)
198    /// * `value` - Behavior configuration value
199    /// * `name` - Behavior name
200    /// * `recursive` - Apply recursively
201    ///
202    /// # Returns
203    /// The created behavior
204    ///
205    /// # Example
206    /// ```no_run
207    /// use files_sdk::{FilesClient, BehaviorHandler};
208    /// use serde_json::json;
209    ///
210    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
211    /// let client = FilesClient::builder().api_key("key").build()?;
212    /// let handler = BehaviorHandler::new(client);
213    /// let webhook_config = json!({"urls": ["https://example.com/hook"]});
214    /// let behavior = handler.create(
215    ///     "/uploads",
216    ///     "webhook",
217    ///     Some(webhook_config),
218    ///     Some("Upload Webhook"),
219    ///     Some(true)
220    /// ).await?;
221    /// # Ok(())
222    /// # }
223    /// ```
224    #[allow(clippy::too_many_arguments)]
225    pub async fn create(
226        &self,
227        path: &str,
228        behavior: &str,
229        value: Option<serde_json::Value>,
230        name: Option<&str>,
231        recursive: Option<bool>,
232    ) -> Result<BehaviorEntity> {
233        let mut request_body = json!({
234            "path": path,
235            "behavior": behavior,
236        });
237
238        if let Some(v) = value {
239            request_body["value"] = v;
240        }
241        if let Some(n) = name {
242            request_body["name"] = json!(n);
243        }
244        if let Some(r) = recursive {
245            request_body["recursive"] = json!(r);
246        }
247
248        let response = self.client.post_raw("/behaviors", request_body).await?;
249        Ok(serde_json::from_value(response)?)
250    }
251
252    /// Update a behavior
253    ///
254    /// # Arguments
255    /// * `id` - Behavior ID
256    /// * `value` - New behavior configuration value
257    /// * `name` - New behavior name
258    /// * `disable_parent_folder_behavior` - Disable parent folder behavior
259    ///
260    /// # Returns
261    /// The updated behavior
262    pub async fn update(
263        &self,
264        id: i64,
265        value: Option<serde_json::Value>,
266        name: Option<&str>,
267        disable_parent_folder_behavior: Option<bool>,
268    ) -> Result<BehaviorEntity> {
269        let mut request_body = json!({});
270
271        if let Some(v) = value {
272            request_body["value"] = v;
273        }
274        if let Some(n) = name {
275            request_body["name"] = json!(n);
276        }
277        if let Some(d) = disable_parent_folder_behavior {
278            request_body["disable_parent_folder_behavior"] = json!(d);
279        }
280
281        let response = self
282            .client
283            .patch_raw(&format!("/behaviors/{}", id), request_body)
284            .await?;
285        Ok(serde_json::from_value(response)?)
286    }
287
288    /// Delete a behavior
289    ///
290    /// # Arguments
291    /// * `id` - Behavior ID
292    pub async fn delete(&self, id: i64) -> Result<()> {
293        self.client
294            .delete_raw(&format!("/behaviors/{}", id))
295            .await?;
296        Ok(())
297    }
298
299    /// Test a webhook behavior
300    ///
301    /// # Arguments
302    /// * `url` - Webhook URL to test
303    /// * `method` - HTTP method (GET or POST)
304    /// * `encoding` - Webhook encoding type
305    /// * `headers` - Custom headers
306    /// * `body` - Request body parameters
307    ///
308    /// # Returns
309    /// Test result
310    #[allow(clippy::too_many_arguments)]
311    pub async fn test_webhook(
312        &self,
313        url: &str,
314        method: Option<&str>,
315        encoding: Option<&str>,
316        headers: Option<serde_json::Value>,
317        body: Option<serde_json::Value>,
318    ) -> Result<serde_json::Value> {
319        let mut request_body = json!({
320            "url": url,
321        });
322
323        if let Some(m) = method {
324            request_body["method"] = json!(m);
325        }
326        if let Some(e) = encoding {
327            request_body["encoding"] = json!(e);
328        }
329        if let Some(h) = headers {
330            request_body["headers"] = h;
331        }
332        if let Some(b) = body {
333            request_body["body"] = b;
334        }
335
336        let response = self
337            .client
338            .post_raw("/behaviors/webhook/test", request_body)
339            .await?;
340        Ok(response)
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[test]
349    fn test_handler_creation() {
350        let client = FilesClient::builder().api_key("test-key").build().unwrap();
351        let _handler = BehaviorHandler::new(client);
352    }
353}