files_sdk/files/
folders.rs

1//! Folder operations
2//!
3//! Provides directory management and navigation operations for Files.com. Folders are
4//! represented as `FileEntity` objects with `type="directory"`.
5//!
6//! # Features
7//!
8//! - List folder contents with pagination
9//! - Create folders (with parent directory creation)
10//! - Delete folders (recursive or non-recursive)
11//! - Search files within folders
12//! - Automatic pagination for large directories
13//!
14//! # Example
15//!
16//! ```no_run
17//! use files_sdk::{FilesClient, FolderHandler};
18//!
19//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
20//! let client = FilesClient::builder()
21//!     .api_key("your-api-key")
22//!     .build()?;
23//!
24//! let handler = FolderHandler::new(client);
25//!
26//! // List root directory
27//! let (files, pagination) = handler.list_folder("/", None, None).await?;
28//! for file in files {
29//!     println!("{}: {}",
30//!         file.file_type.unwrap_or_default(),
31//!         file.path.unwrap_or_default());
32//! }
33//!
34//! // Create a new folder with parent directories
35//! handler.create_folder("/projects/2024/q4", true).await?;
36//!
37//! // Search for files
38//! let (results, _) = handler.search_folder("/", "report", None).await?;
39//! println!("Found {} matching files", results.len());
40//! # Ok(())
41//! # }
42//! ```
43
44use crate::{FileEntity, FilesClient, PaginationInfo, Result};
45use serde_json::json;
46
47/// Handler for folder operations
48///
49/// Provides methods for listing, creating, searching, and managing folders
50/// (directories) in Files.com.
51#[derive(Debug, Clone)]
52pub struct FolderHandler {
53    client: FilesClient,
54}
55
56impl FolderHandler {
57    /// Creates a new FolderHandler
58    ///
59    /// # Arguments
60    ///
61    /// * `client` - FilesClient instance
62    pub fn new(client: FilesClient) -> Self {
63        Self { client }
64    }
65
66    /// List folder contents
67    ///
68    /// Returns files and subdirectories within the specified folder.
69    ///
70    /// # Arguments
71    ///
72    /// * `path` - Folder path to list (empty string for root)
73    /// * `per_page` - Number of items per page (optional, max 10,000)
74    /// * `cursor` - Pagination cursor (optional)
75    ///
76    /// # Returns
77    ///
78    /// Returns a tuple of (files, pagination_info)
79    ///
80    /// # Examples
81    ///
82    /// ```rust,no_run
83    /// use files_sdk::{FilesClient, FolderHandler};
84    ///
85    /// # #[tokio::main]
86    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
87    /// let client = FilesClient::builder()
88    ///     .api_key("your-api-key")
89    ///     .build()?;
90    ///
91    /// let handler = FolderHandler::new(client);
92    /// let (files, pagination) = handler.list_folder("/", None, None).await?;
93    ///
94    /// for file in files {
95    ///     println!("{}: {}", file.file_type.unwrap_or_default(), file.path.unwrap_or_default());
96    /// }
97    ///
98    /// if pagination.has_next() {
99    ///     println!("More results available");
100    /// }
101    /// # Ok(())
102    /// # }
103    /// ```
104    pub async fn list_folder(
105        &self,
106        path: &str,
107        per_page: Option<i32>,
108        cursor: Option<String>,
109    ) -> Result<(Vec<FileEntity>, PaginationInfo)> {
110        let mut endpoint = format!("/folders{}", path);
111        let mut query_params = Vec::new();
112
113        if let Some(per_page) = per_page {
114            query_params.push(format!("per_page={}", per_page));
115        }
116
117        if let Some(cursor) = cursor {
118            query_params.push(format!("cursor={}", cursor));
119        }
120
121        if !query_params.is_empty() {
122            endpoint.push('?');
123            endpoint.push_str(&query_params.join("&"));
124        }
125
126        // Need to get the raw response to access headers
127        let url = format!("{}{}", self.client.inner.base_url, endpoint);
128        let response = reqwest::Client::new()
129            .get(&url)
130            .header("X-FilesAPI-Key", &self.client.inner.api_key)
131            .send()
132            .await?;
133
134        let headers = response.headers().clone();
135        let pagination = PaginationInfo::from_headers(&headers);
136
137        let status = response.status();
138        if !status.is_success() {
139            return Err(crate::FilesError::ApiError {
140                code: status.as_u16(),
141                message: response.text().await.unwrap_or_default(),
142            });
143        }
144
145        let files: Vec<FileEntity> = response.json().await?;
146
147        Ok((files, pagination))
148    }
149
150    /// List all folder contents (auto-pagination)
151    ///
152    /// Automatically handles pagination to retrieve all items in a folder.
153    ///
154    /// # Arguments
155    ///
156    /// * `path` - Folder path to list
157    ///
158    /// # Examples
159    ///
160    /// ```rust,no_run
161    /// # use files_sdk::{FilesClient, FolderHandler};
162    /// # #[tokio::main]
163    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
164    /// # let client = FilesClient::builder().api_key("key").build()?;
165    /// let handler = FolderHandler::new(client);
166    /// let all_files = handler.list_folder_all("/uploads").await?;
167    /// println!("Total files: {}", all_files.len());
168    /// # Ok(())
169    /// # }
170    /// ```
171    pub async fn list_folder_all(&self, path: &str) -> Result<Vec<FileEntity>> {
172        let mut all_files = Vec::new();
173        let mut cursor = None;
174
175        loop {
176            let (mut files, pagination) = self.list_folder(path, Some(1000), cursor).await?;
177            all_files.append(&mut files);
178
179            if pagination.has_next() {
180                cursor = pagination.cursor_next;
181            } else {
182                break;
183            }
184        }
185
186        Ok(all_files)
187    }
188
189    /// Create a new folder
190    ///
191    /// Note: In Files.com, folders are created implicitly when uploading files
192    /// with `mkdir_parents=true`. This method creates an empty folder.
193    ///
194    /// # Arguments
195    ///
196    /// * `path` - Folder path to create
197    /// * `mkdir_parents` - Create parent directories if they don't exist
198    ///
199    /// # Examples
200    ///
201    /// ```rust,no_run
202    /// # use files_sdk::{FilesClient, FolderHandler};
203    /// # #[tokio::main]
204    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
205    /// # let client = FilesClient::builder().api_key("key").build()?;
206    /// let handler = FolderHandler::new(client);
207    /// handler.create_folder("/new/folder", true).await?;
208    /// # Ok(())
209    /// # }
210    /// ```
211    pub async fn create_folder(&self, path: &str, mkdir_parents: bool) -> Result<FileEntity> {
212        let body = json!({
213            "path": path,
214            "mkdir_parents": mkdir_parents,
215        });
216
217        let endpoint = format!("/folders{}", path);
218        let response = self.client.post_raw(&endpoint, body).await?;
219        Ok(serde_json::from_value(response)?)
220    }
221
222    /// Delete a folder
223    ///
224    /// # Arguments
225    ///
226    /// * `path` - Folder path to delete
227    /// * `recursive` - Delete folder and all contents recursively
228    ///
229    /// # Examples
230    ///
231    /// ```rust,no_run
232    /// # use files_sdk::{FilesClient, FolderHandler};
233    /// # #[tokio::main]
234    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
235    /// # let client = FilesClient::builder().api_key("key").build()?;
236    /// let handler = FolderHandler::new(client);
237    /// handler.delete_folder("/old/folder", true).await?;
238    /// # Ok(())
239    /// # }
240    /// ```
241    pub async fn delete_folder(&self, path: &str, recursive: bool) -> Result<()> {
242        let endpoint = if recursive {
243            format!("/folders{}?recursive=true", path)
244        } else {
245            format!("/folders{}", path)
246        };
247
248        self.client.delete_raw(&endpoint).await?;
249        Ok(())
250    }
251
252    /// Search for files within a folder
253    ///
254    /// # Arguments
255    ///
256    /// * `path` - Folder path to search in
257    /// * `search` - Search query string
258    /// * `per_page` - Number of results per page (optional)
259    ///
260    /// # Examples
261    ///
262    /// ```rust,no_run
263    /// # use files_sdk::{FilesClient, FolderHandler};
264    /// # #[tokio::main]
265    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
266    /// # let client = FilesClient::builder().api_key("key").build()?;
267    /// let handler = FolderHandler::new(client);
268    /// let (results, _) = handler.search_folder("/", "report", None).await?;
269    /// println!("Found {} files", results.len());
270    /// # Ok(())
271    /// # }
272    /// ```
273    pub async fn search_folder(
274        &self,
275        path: &str,
276        search: &str,
277        per_page: Option<i32>,
278    ) -> Result<(Vec<FileEntity>, PaginationInfo)> {
279        let mut endpoint = format!("/folders{}?search={}", path, search);
280
281        if let Some(per_page) = per_page {
282            endpoint.push_str(&format!("&per_page={}", per_page));
283        }
284
285        let url = format!("{}{}", self.client.inner.base_url, endpoint);
286        let response = reqwest::Client::new()
287            .get(&url)
288            .header("X-FilesAPI-Key", &self.client.inner.api_key)
289            .send()
290            .await?;
291
292        let headers = response.headers().clone();
293        let pagination = PaginationInfo::from_headers(&headers);
294
295        let status = response.status();
296        if !status.is_success() {
297            return Err(crate::FilesError::ApiError {
298                code: status.as_u16(),
299                message: response.text().await.unwrap_or_default(),
300            });
301        }
302
303        let files: Vec<FileEntity> = response.json().await?;
304
305        Ok((files, pagination))
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn test_handler_creation() {
315        let client = FilesClient::builder().api_key("test-key").build().unwrap();
316        let _handler = FolderHandler::new(client);
317    }
318}