graph_http/traits/
response_ext.rs

1use crate::internal::{
2    copy_async, create_dir_async, FileConfig, HttpResponseBuilderExt, RangeIter, UploadSession,
3};
4use crate::traits::UploadSessionLink;
5use async_trait::async_trait;
6use graph_error::download::AsyncDownloadError;
7use graph_error::{ErrorMessage, ErrorType, GraphFailure, GraphResult};
8use reqwest::header::HeaderMap;
9use reqwest::Response;
10use std::ffi::OsString;
11use std::path::PathBuf;
12use tokio::io::AsyncReadExt;
13
14pub(crate) const MAX_FILE_NAME_LEN: usize = 255;
15
16#[allow(clippy::single_char_pattern)]
17pub(crate) fn parse_content_disposition(headers: &HeaderMap) -> Option<OsString> {
18    if let Some(value) = headers.get("content-disposition") {
19        if let Ok(header) = std::str::from_utf8(value.as_ref()) {
20            let mut v: Vec<&str> = header.split(';').collect();
21            v.retain(|s| !s.is_empty());
22
23            // The filename* indicates that the filename is encoded
24            if let Some(value) = v.iter().find(|s| s.starts_with("filename*=utf-8''")) {
25                let s = value.replace("filename*=utf-8''", "");
26                if let Ok(s) = percent_encoding::percent_decode(s.as_bytes()).decode_utf8() {
27                    return Some(OsString::from(s.to_string().replace('/', "-")));
28                }
29            }
30
31            if let Some(value) = v.last() {
32                if value.trim_start().starts_with("filename=") {
33                    return Some(OsString::from(
34                        value
35                            .replace("\"", "")
36                            .replace("filename=", "")
37                            .replace('/', "-")
38                            .trim(),
39                    ));
40                }
41            }
42        }
43    }
44    None
45}
46
47#[async_trait]
48pub trait ResponseExt {
49    async fn job_status(&self) -> Option<GraphResult<reqwest::Response>>;
50
51    /// # Begin an upload session using any [`std::io::Reader`].<br>
52    ///
53    /// Converts the current request object into an upload session object for uploading large
54    /// files to OneDrive or SharePoint.<br>
55    ///
56    /// This method takes a `reader` object that implements the [std::io::Read] and [Send] traits,
57    /// and returns a [GraphResult] containing an [UploadSession] object.<br>
58    ///
59    /// The [UploadSession] object contains the upload URL for the file, as well as a [RangeIter] iterator
60    /// that can be used to send the file contents to the server in multiple chunks (or "ranges").
61    /// If the upload URL is not found in the response body, this method returns a `GraphFailure`
62    /// with an error message indicating that no upload URL was found.<br>
63    ///
64    ///
65    /// ## Requires reqwest::Response body to be valid JSON
66    /// The body of the reqwest::Response must be valid JSON with an
67    /// [uploadUrl] field.
68    ///
69    /// # Example
70    /// ```rust,ignore
71    /// use graph_rs_sdk::http::{AsyncIterator, ResponseExt};
72    /// use graph_rs_sdk::*;
73    ///
74    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
75    ///
76    /// // Put the path to your file and the file name itself that
77    /// // you want to upload to one drive.
78    /// static LOCAL_FILE_PATH: &str = "/path/to/file/file.txt";
79    ///
80    /// // Parent folder id of where to store this file.
81    /// static DRIVE_PARENT_ID: &str = "PARENT_ID";
82    ///
83    /// // The conflict behavior can be one of: fail, rename, or replace.
84    /// static CONFLICT_BEHAVIOR: &str = "rename";
85    /// #[tokio::main]
86    /// async fn main() -> GraphResult<()> {
87    ///     let client = Graph::new(ACCESS_TOKEN);
88    ///
89    ///     let conflict_behavior = CONFLICT_BEHAVIOR.to_string()
90    ///     let upload = serde_json::json!({
91    ///         "@microsoft.graph.conflictBehavior": Some(conflict_behavior)
92    ///     });
93    ///
94    ///     let response = client
95    ///         .me()
96    ///         .drive()
97    ///         .item_by_path(PATH_IN_ONE_DRIVE)
98    ///         .create_upload_session(&upload)
99    ///         .send()
100    ///         .await
101    ///         .unwrap();
102    ///
103    ///     let file = std::fs::File::open(PATH_IN_ONE_DRIVE)?;
104    ///
105    ///     let mut iter = response.into_upload_session(file).await?;
106    ///
107    ///     while let Some(result) = iter.next().await {
108    ///         let response = result?;
109    ///         println!("{response:#?}");
110    ///     }
111    ///
112    ///     Ok(())
113    /// }
114    /// ```
115    async fn into_upload_session(
116        self,
117        reader: impl std::io::Read + Send,
118    ) -> GraphResult<UploadSession>;
119
120    /// # Begin an upload session using any [tokio::io::AsyncReadExt].<br>
121    ///
122    /// Converts the current request object into an upload session object for uploading large
123    /// files to OneDrive or SharePoint.<br>
124    ///
125    /// This method takes a `reader` object that implements the [tokio::io::AsyncReadExt], [Send], and [Unpin] traits,
126    /// and returns a [GraphResult] containing an [UploadSession] object.<br>
127    ///
128    /// The [UploadSession] object contains the upload URL for the file, as well as a [RangeIter] iterator
129    /// that can be used to send the file contents to the server in multiple chunks (or "ranges").
130    /// If the upload URL is not found in the response body, this method returns a `GraphFailure`
131    /// with an error message indicating that no upload URL was found.<br>
132    ///
133    ///
134    /// ## Requires reqwest::Response body can be deserialized to valid JSON
135    /// The body of the reqwest::Response must be valid JSON with an
136    /// [uploadUrl] field.
137    ///
138    /// # Example
139    /// ```rust,ignore
140    /// use graph_rs_sdk::http::{AsyncIterator, ResponseExt};
141    /// use graph_rs_sdk::*;
142    ///
143    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
144    ///
145    /// // Put the path to your file and the file name itself that
146    /// // you want to upload to one drive.
147    /// static LOCAL_FILE_PATH: &str = "/path/to/file/file.txt";
148    ///
149    /// // Parent folder id of where to store this file.
150    /// static DRIVE_PARENT_ID: &str = "PARENT_ID";
151    ///
152    /// // The conflict behavior can be one of: fail, rename, or replace.
153    /// static CONFLICT_BEHAVIOR: &str = "rename";
154    ///
155    /// #[tokio::main]
156    /// async fn main() -> GraphResult<()> {
157    ///     let client = Graph::new(ACCESS_TOKEN);
158    ///
159    ///     let conflict_behavior = CONFLICT_BEHAVIOR.to_string()
160    ///     let upload = serde_json::json!({
161    ///         "@microsoft.graph.conflictBehavior": Some(conflict_behavior)
162    ///     });
163    ///
164    ///     let response = client
165    ///         .me()
166    ///         .drive()
167    ///         .item_by_path(PATH_IN_ONE_DRIVE)
168    ///         .create_upload_session(&upload)
169    ///         .send()
170    ///         .await
171    ///         .unwrap();
172    ///
173    ///     let file = tokio::fs::File::open(PATH_IN_ONE_DRIVE).await?;
174    ///
175    ///     let mut iter = response.into_upload_session_async_read(file).await?;
176    ///
177    ///     while let Some(result) = iter.next().await {
178    ///         let response = result?;
179    ///         println!("{response:#?}");
180    ///     }
181    ///
182    ///     Ok(())
183    /// }
184    /// ```
185    async fn into_upload_session_async_read(
186        self,
187        reader: impl AsyncReadExt + Send + Unpin,
188    ) -> GraphResult<UploadSession>;
189
190    /// # Downloads the content of the HTTP response and saves it to a file.<br>
191    ///
192    /// This method takes a `file_config` object containing various parameters that control how the
193    /// file is downloaded and saved. The `file_config` object includes the file path, file name,
194    /// whether to create the directory recursively, whether to overwrite existing files, and the
195    /// desired file extension.<br><br>
196    ///
197    /// If `create_dir_all` is set to true (default is true), this method will recursively create the directory
198    /// at the path specified if it doesn't exist yet. If it is set to false and the target directory doesn't exist,
199    /// this method will return an [AsyncDownloadError] with an error message indicating that the
200    /// target directory does not exist.<br><br>
201    ///
202    /// The [`FileConfig::file_name`] parameter can be used to specify a custom file name for the downloaded file.
203    /// If it is not provided, the method will attempt to parse the `Content-Disposition` header to extract the file name.
204    /// If no file name can be obtained from the header, this method will return an [AsyncDownloadError::NoFileName]
205    /// with an error message indicating that no file name was found.<br><br>
206    ///
207    /// If the [`FileConfig::extension`] parameter is set to a non-empty string,
208    /// this method will set the file extension of the downloaded file to the specified value. <br><br>
209    ///
210    /// If the target file already exists and [`overwrite_existing_file`] is set to false,
211    /// this method will return an [AsyncDownloadError::FileExists] with an error message
212    /// indicating that the file already exists and cannot be overwritten. <br><br>
213    ///
214    /// If the file is downloaded and saved successfully, this method returns a [`http::Response<PathBuf>`] object
215    /// containing the path to the downloaded file.
216    ///
217    ///
218    /// # Example
219    ///
220    /// ```rust,ignore
221    /// use graph_rs_sdk::http::{BodyRead, FileConfig};
222    /// use graph_rs_sdk::*;
223    ///
224    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
225    ///
226    /// static ITEM_ID: &str = "ITEM_ID";
227    ///
228    /// static FORMAT: &str = "pdf";
229    ///
230    /// static DOWNLOAD_DIRECTORY: &str = "./examples";
231    ///
232    /// #[tokio::main]
233    /// async fn main() -> GraphResult<()> {
234    ///     let client = Graph::new(ACCESS_TOKEN);
235    ///
236    ///     let response = client
237    ///         .me()
238    ///         .drive()
239    ///         .item(ITEM_ID)
240    ///         .get_items_content()
241    ///         .format(FORMAT)
242    ///         .send()
243    ///         .await?;
244    ///
245    ///     println!("{response:#?}");
246    ///
247    ///     let response2 = response.download(&FileConfig::new(DOWNLOAD_DIRECTORY))
248    ///         .send()
249    ///         .await?;
250    ///
251    ///     let path_buf = response2.body();
252    ///     println!("{:#?}", path_buf.metadata());
253    ///
254    ///     Ok(())
255    /// }
256    /// ```
257    /// <br><br>
258    /// # Example format and rename
259    ///
260    /// ```rust,ignore
261    /// use graph_rs_sdk::http::{BodyRead, FileConfig};
262    /// use graph_rs_sdk::*;
263    /// use std::ffi::OsStr;
264    ///
265    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
266    ///
267    /// static ITEM_ID: &str = "ITEM_ID";
268    ///
269    /// static FORMAT: &str = "pdf";
270    ///
271    /// static DOWNLOAD_DIRECTORY: &str = "./examples";
272    ///
273    /// static FILE_NAME: &str = "new_file_name.pdf";
274    ///
275    /// #[tokio::main]
276    /// async fn main() -> GraphResult<()> {
277    ///     let client = Graph::new(ACCESS_TOKEN);
278    ///
279    ///     let response = client
280    ///         .me()
281    ///         .drive()
282    ///         .item(ITEM_ID)
283    ///         .get_items_content()
284    ///         .format(FORMAT)
285    ///         .send()
286    ///         .await?;
287    ///
288    ///     println!("{response:#?}");
289    ///
290    ///     let file_config = FileConfig::new(DOWNLOAD_DIRECTORY)
291    ///         .file_name(OsStr::new(FILE_NAME));
292    ///
293    ///     let response2 = response.download(file_config)
294    ///         .send()
295    ///         .await?;
296    ///
297    ///     let path_buf = response2.body();
298    ///     println!("{:#?}", path_buf.metadata());
299    ///
300    ///     Ok(())
301    /// }
302    /// ```
303    async fn download(
304        self,
305        file_config: &FileConfig,
306    ) -> Result<http::Response<PathBuf>, AsyncDownloadError>;
307
308    /// If the response is a server error then Microsoft Graph will return
309    /// an error in the response body. The [`ErrorMessage`] type maps to these
310    /// errors and this method deserializes to this type.
311    ///
312    /// Microsoft Graph does not return this error message in all situations so it
313    /// make sure to handle cases where the body could not be deserialized properly.
314    /// ```rust,ignore
315    /// let status = response.status();
316    ///
317    /// if status.is_server_error() || status.is_client_error() {
318    ///     let error_message = response.into_error_message().await.unwrap();
319    ///     println!("{error_message:#?}");
320    ///
321    ///     // This is the same thing as doing
322    ///     let error_message: ErrorMessage = response.json().await.unwrap();
323    /// }
324    /// ```
325    async fn into_graph_error_message(self) -> Result<ErrorMessage, reqwest::Error>;
326
327    /// Microsoft Graph specific status code errors mapped from the response [StatusCode].
328    /// Not all status codes map to a Microsoft Graph error.
329    ///
330    /// Use [`ErrorType::as_str`] to get a short description of the Microsoft Graph specific error.
331    /// ```rust,ignore
332    /// let error_type = response.graph_error_type().unwrap();
333    /// println!("{:#?}", error_type.as_str());
334    /// ```
335    fn graph_error_type(&self) -> Option<ErrorType>;
336}
337
338#[async_trait]
339impl ResponseExt for reqwest::Response {
340    async fn job_status(&self) -> Option<GraphResult<Response>> {
341        let url = self
342            .headers()
343            .get(reqwest::header::LOCATION)?
344            .to_str()
345            .ok()?;
346        let result = reqwest::Client::new()
347            .get(url)
348            .send()
349            .await
350            .map_err(GraphFailure::from);
351
352        Some(result)
353    }
354
355    /// # Begin an upload session using any [`std::io::Reader`].<br>
356    ///
357    /// Converts the current request object into an upload session object for uploading large
358    /// files to OneDrive or SharePoint.<br>
359    ///
360    /// This method takes a `reader` object that implements the [std::io::Read] and [Send] traits,
361    /// and returns a [GraphResult] containing an [UploadSession] object.<br>
362    ///
363    /// The [UploadSession] object contains the upload URL for the file, as well as a [RangeIter] iterator
364    /// that can be used to send the file contents to the server in multiple chunks (or "ranges").
365    /// If the upload URL is not found in the response body, this method returns a `GraphFailure`
366    /// with an error message indicating that no upload URL was found.<br>
367    ///
368    ///
369    /// ## Requires reqwest::Response body to be valid JSON
370    /// The body of the reqwest::Response must be valid JSON with an
371    /// [uploadUrl] field.
372    ///
373    /// # Example
374    /// ```rust,ignore
375    /// use graph_rs_sdk::http::{AsyncIterator, ResponseExt};
376    /// use graph_rs_sdk::*;
377    ///
378    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
379    ///
380    /// // Put the path to your file and the file name itself that
381    /// // you want to upload to one drive.
382    /// static LOCAL_FILE_PATH: &str = "/path/to/file/file.txt";
383    ///
384    /// // Parent folder id of where to store this file.
385    /// static DRIVE_PARENT_ID: &str = "PARENT_ID";
386    ///
387    /// // The conflict behavior can be one of: fail, rename, or replace.
388    /// static CONFLICT_BEHAVIOR: &str = "rename";
389    /// #[tokio::main]
390    /// async fn main() -> GraphResult<()> {
391    ///     let client = Graph::new(ACCESS_TOKEN);
392    ///
393    ///     let conflict_behavior = CONFLICT_BEHAVIOR.to_string();
394    ///     let upload = serde_json::json!({
395    ///         "@microsoft.graph.conflictBehavior": Some(conflict_behavior)
396    ///     });
397    ///
398    ///     let response = client
399    ///         .me()
400    ///         .drive()
401    ///         .item_by_path(PATH_IN_ONE_DRIVE)
402    ///         .create_upload_session(&upload)
403    ///         .send()
404    ///         .await
405    ///         .unwrap();
406    ///
407    ///     let file = std::fs::File::open(PATH_IN_ONE_DRIVE)?;
408    ///
409    ///     let mut iter = response.into_upload_session(file).await?;
410    ///
411    ///     while let Some(result) = iter.next().await {
412    ///         let response = result?;
413    ///         println!("{response:#?}");
414    ///     }
415    ///
416    ///     Ok(())
417    /// }
418    /// ```
419    async fn into_upload_session(
420        self,
421        reader: impl std::io::Read + Send,
422    ) -> GraphResult<UploadSession> {
423        let body: serde_json::Value = self.json().await?;
424        let url = body
425            .upload_session_link()
426            .ok_or_else(|| GraphFailure::not_found("No uploadUrl found in response body"))?;
427
428        let range_iter = RangeIter::from_reader(reader)?;
429        Ok(UploadSession::new(
430            reqwest::Url::parse(url.as_str())?,
431            range_iter,
432        ))
433    }
434
435    /// # Begin an upload session using any [tokio::io::AsyncReadExt].<br>
436    ///
437    /// Converts the current request object into an upload session object for uploading large
438    /// files to OneDrive or SharePoint.<br>
439    ///
440    /// This method takes a `reader` object that implements the [tokio::io::AsyncReadExt], [Send], and [Unpin] traits,
441    /// and returns a [GraphResult] containing an [UploadSession] object.<br>
442    ///
443    /// The [UploadSession] object contains the upload URL for the file, as well as a [RangeIter] iterator
444    /// that can be used to send the file contents to the server in multiple chunks (or "ranges").
445    /// If the upload URL is not found in the response body, this method returns a `GraphFailure`
446    /// with an error message indicating that no upload URL was found.<br>
447    ///
448    ///
449    /// ## Requires reqwest::Response body can be deserialized to valid JSON
450    /// The body of the reqwest::Response must be valid JSON with an
451    /// [uploadUrl] field.
452    ///
453    /// # Example
454    /// ```rust,ignore
455    /// use graph_rs_sdk::http::{AsyncIterator, ResponseExt};
456    /// use graph_rs_sdk::*;
457    ///
458    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
459    ///
460    /// // Put the path to your file and the file name itself that
461    /// // you want to upload to one drive.
462    /// static LOCAL_FILE_PATH: &str = "/path/to/file/file.txt";
463    ///
464    /// // Parent folder id of where to store this file.
465    /// static DRIVE_PARENT_ID: &str = "PARENT_ID";
466    ///
467    /// // The conflict behavior can be one of: fail, rename, or replace.
468    /// static CONFLICT_BEHAVIOR: &str = "rename";
469    ///
470    /// #[tokio::main]
471    /// async fn main() -> GraphResult<()> {
472    ///     let client = Graph::new(ACCESS_TOKEN);
473    ///
474    ///     let conflict_behavior = CONFLICT_BEHAVIOR.to_string();
475    ///     let upload = serde_json::json!({
476    ///         "@microsoft.graph.conflictBehavior": Some(conflict_behavior)
477    ///     });
478    ///
479    ///     let response = client
480    ///         .me()
481    ///         .drive()
482    ///         .item_by_path(PATH_IN_ONE_DRIVE)
483    ///         .create_upload_session(&upload)
484    ///         .send()
485    ///         .await
486    ///         .unwrap();
487    ///
488    ///     let file = tokio::fs::File::open(PATH_IN_ONE_DRIVE).await?;
489    ///
490    ///     let mut iter = response.into_upload_session_async_read(file).await?;
491    ///
492    ///     while let Some(result) = iter.next().await {
493    ///         let response = result?;
494    ///         println!("{response:#?}");
495    ///     }
496    ///
497    ///     Ok(())
498    /// }
499    /// ```
500    async fn into_upload_session_async_read(
501        self,
502        reader: impl AsyncReadExt + Send + Unpin,
503    ) -> GraphResult<UploadSession> {
504        let body: serde_json::Value = self.json().await?;
505        let url = body
506            .upload_session_link()
507            .ok_or_else(|| GraphFailure::not_found("No uploadUrl found in response body"))?;
508
509        let range_iter = RangeIter::from_async_read(reader).await?;
510        Ok(UploadSession::new(
511            reqwest::Url::parse(url.as_str())?,
512            range_iter,
513        ))
514    }
515
516    /// # Downloads the content of the HTTP response and saves it to a file.<br>
517    ///
518    /// This method takes a `file_config` object containing various parameters that control how the
519    /// file is downloaded and saved. The `file_config` object includes the file path, file name,
520    /// whether to create the directory recursively, whether to overwrite existing files, and the
521    /// desired file extension.<br><br>
522    ///
523    /// If `create_dir_all` is set to true, this method will create the directory at the specified
524    /// path if it doesn't exist yet. If it is set to false and the target directory doesn't exist,
525    /// this method will return an `AsyncDownloadError` with an error message indicating that the
526    /// target directory does not exist.<br><br>
527    ///
528    /// The [`FileConfig::file_name`] parameter can be used to specify a custom file name for the downloaded file.
529    /// If it is not provided, the method will attempt to parse the `Content-Disposition` header to extract the file name.
530    /// If no file name can be obtained from the header, this method will return an [AsyncDownloadError::NoFileName]
531    /// with an error message indicating that no file name was found.<br><br>
532    ///
533    /// If the [`FileConfig::extension`] parameter is set to a non-empty string,
534    /// this method will set the file extension of the downloaded file to the specified value. <br><br>
535    ///
536    /// If the target file already exists and [`overwrite_existing_file`] is set to false,
537    /// this method will return an [AsyncDownloadError::FileExists] with an error message
538    /// indicating that the file already exists and cannot be overwritten. <br><br>
539    ///
540    /// If the file is downloaded and saved successfully, this method returns a [`http::Response<PathBuf>`] object
541    /// containing the path to the downloaded file.
542    ///
543    ///
544    /// # Example
545    ///
546    /// ```rust,ignore
547    /// use graph_rs_sdk::http::{BodyRead, FileConfig};
548    /// use graph_rs_sdk::*;
549    ///
550    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
551    ///
552    /// static ITEM_ID: &str = "ITEM_ID";
553    ///
554    /// static FORMAT: &str = "pdf";
555    ///
556    /// static DOWNLOAD_DIRECTORY: &str = "./examples";
557    ///
558    /// #[tokio::main]
559    /// async fn main() -> GraphResult<()> {
560    ///     let client = Graph::new(ACCESS_TOKEN);
561    ///
562    ///     let response = client
563    ///         .me()
564    ///         .drive()
565    ///         .item(ITEM_ID)
566    ///         .get_items_content()
567    ///         .format(FORMAT)
568    ///         .send()
569    ///         .await?;
570    ///
571    ///     println!("{response:#?}");
572    ///
573    ///     let response2 = response.download(&FileConfig::new(DOWNLOAD_DIRECTORY))
574    ///         .send()
575    ///         .await?;
576    ///
577    ///     let path_buf = response2.body();
578    ///     println!("{:#?}", path_buf.metadata());
579    ///
580    ///     Ok(())
581    /// }
582    /// ```
583    /// <br><br>
584    /// # Example format and rename
585    ///
586    /// ```rust,ignore
587    /// use graph_rs_sdk::http::{BodyRead, FileConfig};
588    /// use graph_rs_sdk::*;
589    /// use std::ffi::OsStr;
590    ///
591    /// static ACCESS_TOKEN: &str = "ACCESS_TOKEN";
592    ///
593    /// static ITEM_ID: &str = "ITEM_ID";
594    ///
595    /// static FORMAT: &str = "pdf";
596    ///
597    /// static DOWNLOAD_DIRECTORY: &str = "./examples";
598    ///
599    /// static FILE_NAME: &str = "new_file_name.pdf";
600    ///
601    /// #[tokio::main]
602    /// async fn main() -> GraphResult<()> {
603    ///     let client = Graph::new(ACCESS_TOKEN);
604    ///
605    ///     let response = client
606    ///         .me()
607    ///         .drive()
608    ///         .item(ITEM_ID)
609    ///         .get_items_content()
610    ///         .format(FORMAT)
611    ///         .send()
612    ///         .await?;
613    ///
614    ///     println!("{response:#?}");
615    ///
616    ///     let file_config = FileConfig::new(DOWNLOAD_DIRECTORY)
617    ///         .file_name(OsStr::new(FILE_NAME));
618    ///
619    ///     let response2 = response.download(file_config)
620    ///         .send()
621    ///         .await?;
622    ///
623    ///     let path_buf = response2.body();
624    ///     println!("{:#?}", path_buf.metadata());
625    ///
626    ///     Ok(())
627    /// }
628    /// ```
629    async fn download(
630        self,
631        file_config: &FileConfig,
632    ) -> Result<http::Response<PathBuf>, AsyncDownloadError> {
633        let path = file_config.path.clone();
634        let file_name = file_config.file_name.clone();
635        let create_dir_all = file_config.create_directory_all;
636        let overwrite_existing_file = file_config.overwrite_existing_file;
637        let extension = file_config.extension.clone();
638
639        if create_dir_all {
640            create_dir_async(path.as_path()).await?;
641        } else if !path.exists() {
642            return Err(AsyncDownloadError::TargetDoesNotExist(
643                path.to_string_lossy().to_string(),
644            ));
645        }
646
647        let path = {
648            if let Some(name) = file_name.or_else(|| parse_content_disposition(self.headers())) {
649                if name.len() > MAX_FILE_NAME_LEN {
650                    return Err(AsyncDownloadError::FileNameTooLong);
651                }
652                path.join(name)
653            } else {
654                return Err(AsyncDownloadError::NoFileName);
655            }
656        };
657
658        if let Some(ext) = extension.as_ref() {
659            path.with_extension(ext.as_os_str());
660        }
661
662        if path.exists() && !overwrite_existing_file {
663            return Err(AsyncDownloadError::FileExists(
664                path.to_string_lossy().to_string(),
665            ));
666        }
667
668        let status = self.status();
669        let url = self.url().clone();
670        let _headers = self.headers().clone();
671        let version = self.version();
672
673        Ok(http::Response::builder()
674            .url(url)
675            .status(http::StatusCode::from(&status))
676            .version(version)
677            .body(copy_async(path, self).await?)?)
678    }
679
680    /// If the response is a server error then Microsoft Graph will return
681    /// an error in the response body. The [`ErrorMessage`] type maps to these
682    /// errors and this method deserializes to this type.
683    ///
684    /// Microsoft Graph does not return this error message in all situations so it
685    /// make sure to handle cases where the body could not be deserialized properly.
686    /// ```rust,ignore
687    /// let status = response.status();
688    ///
689    /// if status.is_server_error() || status.is_client_error() {
690    ///     let error_message = response.into_error_message().await.unwrap();
691    ///     println!("{error_message:#?}");
692    ///
693    ///     // This is the same thing as doing
694    ///     let error_message: ErrorMessage = response.json().await.unwrap();
695    /// }
696    /// ```
697    async fn into_graph_error_message(self) -> Result<ErrorMessage, reqwest::Error> {
698        self.json().await
699    }
700
701    /// Microsoft Graph specific status code errors mapped from the response [StatusCode].
702    /// Not all status codes map to a Microsoft Graph error.
703    ///
704    /// Use [`ErrorType::as_str`] to get a short description of the Microsoft Graph specific error.
705    /// ```rust,ignore
706    /// let error_type = response.graph_error_type().unwrap();
707    /// println!("{:#?}", error_type.as_str());
708    /// ```
709    fn graph_error_type(&self) -> Option<ErrorType> {
710        let status = self.status();
711        ErrorType::from_u16(status.as_u16())
712    }
713}