files_sdk/
types.rs

1//! Common types for Files.com API
2//!
3//! This module contains shared types used across multiple endpoints,
4//! including file entities, folder entities, pagination information,
5//! and upload-related types.
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Represents a file or directory in Files.com
11///
12/// This is the primary entity returned by most file operations.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct FileEntity {
15    /// File/folder path
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub path: Option<String>,
18
19    /// Display name of file/folder
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub display_name: Option<String>,
22
23    /// Type: "file" or "directory"
24    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
25    pub file_type: Option<String>,
26
27    /// Size in bytes
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub size: Option<i64>,
30
31    /// Creation timestamp
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub created_at: Option<String>,
34
35    /// Modification time
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub mtime: Option<String>,
38
39    /// Provided modification time (custom)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub provided_mtime: Option<String>,
42
43    /// CRC32 checksum
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub crc32: Option<String>,
46
47    /// MD5 hash
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub md5: Option<String>,
50
51    /// SHA1 hash
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub sha1: Option<String>,
54
55    /// SHA256 hash
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub sha256: Option<String>,
58
59    /// MIME type
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub mime_type: Option<String>,
62
63    /// Storage region
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub region: Option<String>,
66
67    /// Permissions string
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub permissions: Option<String>,
70
71    /// Whether subfolders are locked
72    #[serde(rename = "subfolders_locked?", skip_serializing_if = "Option::is_none")]
73    pub subfolders_locked: Option<bool>,
74
75    /// Whether file is locked
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub is_locked: Option<bool>,
78
79    /// Download URI (temporary URL for downloading)
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub download_uri: Option<String>,
82
83    /// Priority color
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub priority_color: Option<String>,
86
87    /// Preview ID
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub preview_id: Option<i64>,
90
91    /// Preview information
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub preview: Option<String>,
94
95    /// Custom metadata (max 32 keys)
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub custom_metadata: Option<HashMap<String, String>>,
98
99    /// ID of user who created this
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub created_by_id: Option<i64>,
102
103    /// ID of API key that created this
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub created_by_api_key_id: Option<i64>,
106
107    /// ID of automation that created this
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub created_by_automation_id: Option<i64>,
110
111    /// ID of bundle registration that created this
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub created_by_bundle_registration_id: Option<i64>,
114
115    /// ID of inbox that created this
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub created_by_inbox_id: Option<i64>,
118
119    /// ID of remote server that created this
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub created_by_remote_server_id: Option<i64>,
122
123    /// ID of remote server sync that created this
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub created_by_remote_server_sync_id: Option<i64>,
126
127    /// ID of AS2 incoming message that created this
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub created_by_as2_incoming_message_id: Option<i64>,
130
131    /// ID of user who last modified this
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub last_modified_by_id: Option<i64>,
134
135    /// ID of API key that last modified this
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub last_modified_by_api_key_id: Option<i64>,
138
139    /// ID of automation that last modified this
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub last_modified_by_automation_id: Option<i64>,
142
143    /// ID of bundle registration that last modified this
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub last_modified_by_bundle_registration_id: Option<i64>,
146
147    /// ID of remote server that last modified this
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub last_modified_by_remote_server_id: Option<i64>,
150
151    /// ID of remote server sync that last modified this
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub last_modified_by_remote_server_sync_id: Option<i64>,
154}
155
156/// Represents upload information for a file part
157///
158/// Returned by the begin_upload operation to provide URLs and parameters
159/// for uploading file data.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct FileUploadPartEntity {
162    /// URI to upload this part to
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub upload_uri: Option<String>,
165
166    /// HTTP method to use (usually "PUT")
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub http_method: Option<String>,
169
170    /// Additional headers to include in upload request
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub headers: Option<HashMap<String, String>>,
173
174    /// Additional HTTP parameters to send
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub parameters: Option<HashMap<String, String>>,
177
178    /// Part number for multi-part uploads
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub part_number: Option<i32>,
181
182    /// Size in bytes for this part
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub partsize: Option<i64>,
185
186    /// Size in bytes for the next part
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub next_partsize: Option<i64>,
189
190    /// Reference identifier for this upload
191    #[serde(rename = "ref", skip_serializing_if = "Option::is_none")]
192    pub ref_: Option<String>,
193
194    /// Type of upload action
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub action: Option<String>,
197
198    /// Whether multiple parts can be uploaded in parallel
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub parallel_parts: Option<bool>,
201
202    /// Whether parts can be retried
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub retry_parts: Option<bool>,
205
206    /// Number of parts in the upload
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub available_parts: Option<i32>,
209
210    /// When this upload URL expires
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub expires: Option<String>,
213
214    /// Content-Type and file to send
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub send: Option<HashMap<String, String>>,
217
218    /// File path being uploaded to
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub path: Option<String>,
221
222    /// Whether to ask about overwrites
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub ask_about_overwrites: Option<bool>,
225}
226
227/// Represents a folder (directory) in Files.com
228///
229/// Alias for FileEntity since folders are represented as files with type="directory"
230pub type FolderEntity = FileEntity;
231
232/// Pagination information from response headers
233///
234/// Files.com uses cursor-based pagination with cursors provided in response headers.
235#[derive(Debug, Clone, Default)]
236pub struct PaginationInfo {
237    /// Cursor for the next page of results
238    pub cursor_next: Option<String>,
239
240    /// Cursor for the previous page of results
241    pub cursor_prev: Option<String>,
242}
243
244impl PaginationInfo {
245    /// Creates pagination info from response headers
246    pub fn from_headers(headers: &reqwest::header::HeaderMap) -> Self {
247        let cursor_next = headers
248            .get("X-Files-Cursor-Next")
249            .and_then(|v| v.to_str().ok())
250            .map(String::from);
251
252        let cursor_prev = headers
253            .get("X-Files-Cursor-Prev")
254            .and_then(|v| v.to_str().ok())
255            .map(String::from);
256
257        Self {
258            cursor_next,
259            cursor_prev,
260        }
261    }
262
263    /// Whether there is a next page
264    pub fn has_next(&self) -> bool {
265        self.cursor_next.is_some()
266    }
267
268    /// Whether there is a previous page
269    pub fn has_prev(&self) -> bool {
270        self.cursor_prev.is_some()
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_file_entity_deserialize() {
280        let json = r#"{
281            "path": "/test/file.txt",
282            "display_name": "file.txt",
283            "type": "file",
284            "size": 1024
285        }"#;
286
287        let entity: FileEntity = serde_json::from_str(json).unwrap();
288        assert_eq!(entity.path, Some("/test/file.txt".to_string()));
289        assert_eq!(entity.display_name, Some("file.txt".to_string()));
290        assert_eq!(entity.file_type, Some("file".to_string()));
291        assert_eq!(entity.size, Some(1024));
292    }
293
294    #[test]
295    fn test_pagination_info_empty() {
296        let headers = reqwest::header::HeaderMap::new();
297        let info = PaginationInfo::from_headers(&headers);
298        assert!(!info.has_next());
299        assert!(!info.has_prev());
300    }
301
302    #[test]
303    fn test_pagination_info_with_next() {
304        let mut headers = reqwest::header::HeaderMap::new();
305        headers.insert("X-Files-Cursor-Next", "next-cursor".parse().unwrap());
306
307        let info = PaginationInfo::from_headers(&headers);
308        assert!(info.has_next());
309        assert!(!info.has_prev());
310        assert_eq!(info.cursor_next, Some("next-cursor".to_string()));
311    }
312}