1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum EntryType {
18 File,
20 Directory,
22}
23
24impl EntryType {
25 pub fn is_file(&self) -> bool {
27 matches!(self, EntryType::File)
28 }
29
30 pub fn is_directory(&self) -> bool {
32 matches!(self, EntryType::Directory)
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct FileEntity {
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub path: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub display_name: Option<String>,
48
49 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
51 pub file_type: Option<EntryType>,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub size: Option<i64>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub created_at: Option<String>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub mtime: Option<String>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub provided_mtime: Option<String>,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub crc32: Option<String>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub md5: Option<String>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub sha1: Option<String>,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub sha256: Option<String>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub mime_type: Option<String>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub region: Option<String>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub permissions: Option<String>,
96
97 #[serde(rename = "subfolders_locked?", skip_serializing_if = "Option::is_none")]
99 pub subfolders_locked: Option<bool>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub is_locked: Option<bool>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub download_uri: Option<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub priority_color: Option<String>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub preview_id: Option<i64>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub preview: Option<String>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub custom_metadata: Option<HashMap<String, String>>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub created_by_id: Option<i64>,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub created_by_api_key_id: Option<i64>,
132
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub created_by_automation_id: Option<i64>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub created_by_bundle_registration_id: Option<i64>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub created_by_inbox_id: Option<i64>,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub created_by_remote_server_id: Option<i64>,
148
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub created_by_remote_server_sync_id: Option<i64>,
152
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub created_by_as2_incoming_message_id: Option<i64>,
156
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub last_modified_by_id: Option<i64>,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub last_modified_by_api_key_id: Option<i64>,
164
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub last_modified_by_automation_id: Option<i64>,
168
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub last_modified_by_bundle_registration_id: Option<i64>,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub last_modified_by_remote_server_id: Option<i64>,
176
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub last_modified_by_remote_server_sync_id: Option<i64>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct FileUploadPartEntity {
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub upload_uri: Option<String>,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub http_method: Option<String>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub headers: Option<HashMap<String, String>>,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub parameters: Option<HashMap<String, String>>,
203
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub part_number: Option<i32>,
207
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub partsize: Option<i64>,
211
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub next_partsize: Option<i64>,
215
216 #[serde(rename = "ref", skip_serializing_if = "Option::is_none")]
218 pub ref_: Option<String>,
219
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub action: Option<String>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub parallel_parts: Option<bool>,
227
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub retry_parts: Option<bool>,
231
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub available_parts: Option<i32>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub expires: Option<String>,
239
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub send: Option<HashMap<String, String>>,
243
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub path: Option<String>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub ask_about_overwrites: Option<bool>,
251}
252
253pub type FolderEntity = FileEntity;
257
258#[derive(Debug, Clone, Default)]
262pub struct PaginationInfo {
263 pub cursor_next: Option<String>,
265
266 pub cursor_prev: Option<String>,
268}
269
270impl PaginationInfo {
271 pub fn from_headers(headers: &reqwest::header::HeaderMap) -> Self {
273 let cursor_next = headers
274 .get("X-Files-Cursor-Next")
275 .and_then(|v| v.to_str().ok())
276 .map(String::from);
277
278 let cursor_prev = headers
279 .get("X-Files-Cursor-Prev")
280 .and_then(|v| v.to_str().ok())
281 .map(String::from);
282
283 Self {
284 cursor_next,
285 cursor_prev,
286 }
287 }
288
289 pub fn has_next(&self) -> bool {
291 self.cursor_next.is_some()
292 }
293
294 pub fn has_prev(&self) -> bool {
296 self.cursor_prev.is_some()
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_file_entity_deserialize() {
306 let json = r#"{
307 "path": "/test/file.txt",
308 "display_name": "file.txt",
309 "type": "file",
310 "size": 1024
311 }"#;
312
313 let entity: FileEntity = serde_json::from_str(json).unwrap();
314 assert_eq!(entity.path, Some("/test/file.txt".to_string()));
315 assert_eq!(entity.display_name, Some("file.txt".to_string()));
316 assert_eq!(entity.file_type, Some(EntryType::File));
317 assert_eq!(entity.size, Some(1024));
318 }
319
320 #[test]
321 fn test_pagination_info_empty() {
322 let headers = reqwest::header::HeaderMap::new();
323 let info = PaginationInfo::from_headers(&headers);
324 assert!(!info.has_next());
325 assert!(!info.has_prev());
326 }
327
328 #[test]
329 fn test_pagination_info_with_next() {
330 let mut headers = reqwest::header::HeaderMap::new();
331 headers.insert("X-Files-Cursor-Next", "next-cursor".parse().unwrap());
332
333 let info = PaginationInfo::from_headers(&headers);
334 assert!(info.has_next());
335 assert!(!info.has_prev());
336 assert_eq!(info.cursor_next, Some("next-cursor".to_string()));
337 }
338}