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}