1use serde::Deserialize;
19use serde::Serialize;
20
21#[derive(Debug, Deserialize)]
22pub struct GraphOAuthRefreshTokenResponseBody {
23 pub access_token: String,
24 pub refresh_token: String,
25 pub expires_in: i64, }
27
28pub const GENERAL_SELECT_PARAM: &str =
32 "$select=id,name,lastModifiedDateTime,eTag,size,parentReference,folder,file";
33
34pub const VERSION_SELECT_PARAM: &str = "$select=id,size,lastModifiedDateTime";
38
39#[derive(Debug, Serialize, Deserialize)]
40pub struct GraphApiOneDriveListResponse {
41 #[serde(rename = "@odata.nextLink")]
42 pub next_link: Option<String>,
43 pub value: Vec<OneDriveItem>,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct OneDriveItem {
51 pub id: String,
52 pub name: String,
53 pub last_modified_date_time: String,
54 pub e_tag: String,
55 pub size: i64,
56 pub parent_reference: ParentReference,
57 #[serde(flatten)]
58 pub item_type: ItemType,
59 pub versions: Option<Vec<OneDriveItemVersion>>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct ParentReference {
65 pub path: String,
66 pub drive_id: String,
67 pub id: String,
68}
69
70#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
74#[serde(untagged)]
75pub enum ItemType {
76 Folder { folder: Folder },
77 File { file: File },
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81#[serde(rename_all = "camelCase")]
82pub struct File {
83 mime_type: String,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87#[serde(rename_all = "camelCase")]
88pub struct Folder {
89 child_count: i32,
90}
91
92#[derive(Debug, Serialize, Deserialize)]
93pub struct GraphApiOneDriveVersionsResponse {
94 pub value: Vec<OneDriveItemVersion>,
95}
96
97#[derive(Debug, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct OneDriveItemVersion {
103 pub id: String,
104 pub last_modified_date_time: String,
105 pub size: i64,
106}
107
108pub const REPLACE_EXISTING_ITEM_WHEN_CONFLICT: &str = "replace";
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct CreateDirPayload {
114 #[serde(rename = "@microsoft.graph.conflictBehavior")]
115 conflict_behavior: String,
116 name: String,
117 folder: EmptyStruct,
118}
119
120impl CreateDirPayload {
121 pub fn new(name: String) -> Self {
122 Self {
123 conflict_behavior: REPLACE_EXISTING_ITEM_WHEN_CONFLICT.to_string(),
124 name,
125 folder: EmptyStruct {},
126 }
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131struct EmptyStruct {}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134struct FileUploadItem {
135 #[serde(rename = "@microsoft.graph.conflictBehavior")]
136 conflict_behavior: String,
137 name: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct OneDriveUploadSessionCreationResponseBody {
143 pub upload_url: String,
144 pub expiration_date_time: String,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct OneDriveUploadSessionCreationRequestBody {
149 item: FileUploadItem,
150}
151
152impl OneDriveUploadSessionCreationRequestBody {
153 pub fn new(path: String) -> Self {
154 OneDriveUploadSessionCreationRequestBody {
155 item: FileUploadItem {
156 conflict_behavior: REPLACE_EXISTING_ITEM_WHEN_CONFLICT.to_string(),
157 name: path,
158 },
159 }
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct OneDrivePatchRequestBody {
167 pub parent_reference: ParentReference,
168 pub name: String,
169}
170
171#[derive(Debug, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct OneDriveMonitorStatus {
174 pub percentage_complete: f64, pub status: String,
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_parse_one_drive_list_response_json() {
184 let data = r#"{
185 "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('opendal@outlook.com')/drive/root/children(id,name,lastModifiedDateTime,eTag,size,parentReference,folder,file)",
186 "value": [
187 {
188 "@odata.etag": "\"{3B131E1C-7D81-20AF-80D0-450D00000000},10\"",
189 "eTag": "\"{3B131E1C-7D81-20AF-80D0-450D00000000},10\"",
190 "id": "A0AA0A000A000A0A!3397",
191 "lastModifiedDateTime": "2025-02-23T11:45:26Z",
192 "name": "empty_folder",
193 "size": 0,
194 "parentReference": {
195 "driveType": "personal",
196 "driveId": "A0AA0A000A000A0A",
197 "id": "A0AA0A000A000A0A!sea8cc6beffdb43d7976fbc7da445c639",
198 "name": "Documents",
199 "path": "/drive/root:",
200 "siteId": "5f1f11f3-a6b4-4414-aee0-215c774f80db"
201 },
202 "folder": {
203 "childCount": 0,
204 "view": {
205 "sortBy": "name",
206 "sortOrder": "ascending",
207 "viewType": "thumbnails"
208 }
209 }
210 },
211 {
212 "@odata.etag": "\"{3B131E1C-7D81-20AF-80D0-710000000000},6\"",
213 "eTag": "\"{3B131E1C-7D81-20AF-80D0-710000000000},6\"",
214 "id": "A0AA0A000A000A0A!113",
215 "lastModifiedDateTime": "2025-02-16T19:48:39Z",
216 "name": "folder_a",
217 "size": 10560537,
218 "parentReference": {
219 "driveType": "personal",
220 "driveId": "A0AA0A000A000A0A",
221 "id": "A0AA0A000A000A0A!sea8cc6beffdb43d7976fbc7da445c639",
222 "name": "Documents",
223 "path": "/drive/root:",
224 "siteId": "5f1f11f3-a6b4-4414-aee0-215c774f80db"
225 },
226 "folder": {
227 "childCount": 5,
228 "view": {
229 "sortBy": "name",
230 "sortOrder": "ascending",
231 "viewType": "thumbnails"
232 }
233 }
234 }
235 ]
236 }"#;
237
238 let response: GraphApiOneDriveListResponse = serde_json::from_str(data).unwrap();
239 assert_eq!(response.value.len(), 2);
240 let item = &response.value[0];
241 assert_eq!(item.name, "empty_folder");
242 assert_eq!(item.last_modified_date_time, "2025-02-23T11:45:26Z");
243 assert_eq!(item.e_tag, "\"{3B131E1C-7D81-20AF-80D0-450D00000000},10\"");
244 assert_eq!(item.size, 0);
245 assert_eq!(item.parent_reference.path, "/drive/root:");
246 if let ItemType::Folder { folder, .. } = &item.item_type {
247 assert_eq!(folder.child_count, 0);
248 } else {
249 panic!("item_type is not a folder");
250 }
251 }
252
253 #[test]
254 fn test_parse_one_drive_list_response_with_next_link_json() {
255 let response_json = r#"{
256 "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('opendal@outlook.com')/drive/root/children(id,name,lastModifiedDateTime,eTag,size,parentReference,folder,file)",
257 "@odata.nextLink": "https://graph.microsoft.com/v1.0/me/drive/root/children?$select=id%2cname%2clastModifiedDateTime%2ceTag%2csize%2cparentReference%2cfolder%2cfile&$top=2&$skiptoken=UGFnZWQ9VFJVRSZwX1NvcnRCZWhhdmlvcj0xJnBfRmlsZUxlYWZSZWY9Zm9sZGVyX2EmcF9JRD03MDAz",
258 "value": [
259 {
260 "@odata.etag": "\"{3B131E1C-7D81-20AF-80D0-450D00000000},10\"",
261 "eTag": "\"{3B131E1C-7D81-20AF-80D0-450D00000000},10\"",
262 "id": "A0AA0A000A000A0A!3397",
263 "lastModifiedDateTime": "2025-02-23T11:45:26Z",
264 "name": "empty_folder",
265 "size": 0,
266 "parentReference": {
267 "driveType": "personal",
268 "driveId": "A0AA0A000A000A0A",
269 "id": "A0AA0A000A000A0A!sea8cc6beffdb43d7976fbc7da445c639",
270 "name": "Documents",
271 "path": "/drive/root:",
272 "siteId": "5f1f11f3-a6b4-4414-aee0-215c774f80db"
273 },
274 "folder": {
275 "childCount": 0,
276 "view": {
277 "sortBy": "name",
278 "sortOrder": "ascending",
279 "viewType": "thumbnails"
280 }
281 }
282 }
283 ]
284 }"#;
285
286 let response: GraphApiOneDriveListResponse = serde_json::from_str(response_json).unwrap();
287 assert_eq!(response.value.len(), 1);
288 let item = &response.value[0];
289 if let ItemType::Folder { folder, .. } = &item.item_type {
290 assert_eq!(folder.child_count, 0);
291 } else {
292 panic!("item_type is not a folder");
293 }
294 }
295
296 #[test]
297 fn test_parse_one_drive_file_json() {
298 let data = r#"{
299 "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('opendal%40outlook.com')/drive/root(id,name,lastModifiedDateTime,eTag,size,parentReference,folder,file)/$entity",
300 "@odata.etag": "\"{3B131E1C-7D81-20AF-80D0-720000000000},2\"",
301 "eTag": "\"{3B131E1C-7D81-20AF-80D0-720000000000},2\"",
302 "id": "A0AA0A000A000A0A!114",
303 "lastModifiedDateTime": "2025-02-16T19:49:05Z",
304 "name": "filename.txt",
305 "size": 3,
306 "parentReference": {
307 "driveType": "personal",
308 "driveId": "A0AA0A000A000A0A",
309 "id": "A0AA0A000A000A0A!113",
310 "name": "folder_a",
311 "path": "/drive/root:/folder_a",
312 "siteId": "5f1f11f3-a6b4-4414-aee0-215c774f80db"
313 },
314 "file": {
315 "mimeType": "text/plain",
316 "hashes": {
317 "quickXorHash": "79jFLwAAAAAAAAAAAwAAAAAAAAA=",
318 "sha1Hash": "57218C316B6921E2CD61027A2387EDC31A2D9471",
319 "sha256Hash": "F1945CD6C19E56B3C1C78943EF5EC18116907A4CA1EFC40A57D48AB1DB7ADFC5"
320 }
321 }
322 }"#;
323
324 let item: OneDriveItem = serde_json::from_str(data).unwrap();
325 assert_eq!(item.name, "filename.txt");
326 assert_eq!(item.last_modified_date_time, "2025-02-16T19:49:05Z");
327 assert_eq!(item.e_tag, "\"{3B131E1C-7D81-20AF-80D0-720000000000},2\"");
328 assert_eq!(item.size, 3);
329 assert_eq!(item.parent_reference.id, "A0AA0A000A000A0A!113");
330 assert!(item.versions.is_none());
331 if let ItemType::File { file, .. } = &item.item_type {
332 assert_eq!(file.mime_type, "text/plain");
333 } else {
334 panic!("item_type is not a file");
335 }
336 }
337
338 #[test]
339 fn test_parse_one_drive_file_with_version_json() {
340 let data = r#"{
341 "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('opendal%40outlook.com')/drive/root(id,name,lastModifiedDateTime,eTag,size,parentReference,folder,file)/$entity",
342 "@odata.etag": "\"{3B131E1C-7D81-20AF-80D0-720000000000},2\"",
343 "eTag": "\"{3B131E1C-7D81-20AF-80D0-720000000000},2\"",
344 "id": "A0AA0A000A000A0A!114",
345 "lastModifiedDateTime": "2025-02-16T19:49:05Z",
346 "name": "filename.txt",
347 "size": 3,
348 "parentReference": {
349 "driveType": "personal",
350 "driveId": "A0AA0A000A000A0A",
351 "id": "A0AA0A000A000A0A!113",
352 "name": "folder_a",
353 "path": "/drive/root:/folder_a",
354 "siteId": "5f1f11f3-a6b4-4414-aee0-215c774f80db"
355 },
356 "file": {
357 "mimeType": "text/plain",
358 "hashes": {
359 "quickXorHash": "79jFLwAAAAAAAAAAAwAAAAAAAAA=",
360 "sha1Hash": "57218C316B6921E2CD61027A2387EDC31A2D9471",
361 "sha256Hash": "F1945CD6C19E56B3C1C78943EF5EC18116907A4CA1EFC40A57D48AB1DB7ADFC5"
362 }
363 },
364 "versions@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('opendal%40outlook.com')/drive/root/versions",
365 "versions": [
366 {
367 "@microsoft.graph.downloadUrl": "https://my.microsoftpersonalcontent.com/personal/A0AA0A000A000A0A/_layouts/15/download.aspx?UniqueId=3b131e1c-7d81-20af-80d0-720000000000&Translate=false&tempauth=v1e.a&ApiVersion=2.0",
368 "id": "1.0",
369 "lastModifiedDateTime": "2025-02-16T19:49:05Z",
370 "size": 3,
371 "lastModifiedBy": {
372 "user": {
373 "email": "erickgdev@outlook.com",
374 "displayName": "erickgdev@outlook.com"
375 }
376 }
377 }
378 ]
379 }"#;
380
381 let item: OneDriveItem = serde_json::from_str(data).unwrap();
382 let versions = item.versions.expect("Versions present");
383 assert_eq!("1.0", versions[0].id);
384 assert_eq!("2025-02-16T19:49:05Z", versions[0].last_modified_date_time);
385 }
386
387 #[test]
388 fn test_parse_one_drive_monitor_status_json() {
389 let data = r#"{
390 "@odata.context": "https://my.microsoftpersonalcontent.com/personal/A0AA0A000A000A0A/_api/v2.0/$metadata#oneDrive.asynchronousOperationStatus",
391 "percentageComplete": 100.0,
392 "resourceId": "01JP3NYHGSBJ7R42UN65HZ333HZFWQTGL4",
393 "status": "completed"
394 }"#;
395
396 let response: OneDriveMonitorStatus = serde_json::from_str(data).unwrap();
397 assert_eq!(response.status, "completed");
398 }
399
400 #[test]
401 fn test_parse_one_drive_item_versions_json() {
402 let data = r#"{
403 "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('erickgdev%40outlook.com')/drive/root/versions(id,size,lastModifiedDateTime)",
404 "value": [
405 {
406 "id": "2.0",
407 "lastModifiedDateTime": "2025-03-16T17:02:49Z",
408 "size": 74758
409 },
410 {
411 "id": "1.0",
412 "lastModifiedDateTime": "2025-03-12T21:59:54Z",
413 "size": 74756
414 }
415 ]
416 }"#;
417
418 let response: GraphApiOneDriveVersionsResponse = serde_json::from_str(data).unwrap();
419 assert_eq!(response.value.len(), 2);
420 let version = &response.value[0];
421 assert_eq!(version.id, "2.0");
422 assert_eq!(version.last_modified_date_time, "2025-03-16T17:02:49Z");
423 assert_eq!(version.size, 74758);
424 }
425}