files_sdk/files/
folders.rs

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