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}