changeset 106:4dc7d2a7ff71

Enable dynamic downloads (depending on content-type)
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 25 Oct 2020 08:54:24 +0100
parents 2f22414d6cea
children 6db793957730
files async-google-apis-common/src/error.rs async-google-apis-common/src/http.rs
diffstat 2 files changed, 43 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/async-google-apis-common/src/error.rs	Sun Oct 25 08:53:53 2020 +0100
+++ b/async-google-apis-common/src/error.rs	Sun Oct 25 08:54:24 2020 +0100
@@ -3,6 +3,7 @@
     HTTPResponseError(hyper::StatusCode, String),
     RedirectError(String),
     InputDataError(String),
+    DataAvailableError(String),
 }
 
 impl std::error::Error for ApiError {}
--- a/async-google-apis-common/src/http.rs	Sun Oct 25 08:53:53 2020 +0100
+++ b/async-google-apis-common/src/http.rs	Sun Oct 25 08:54:24 2020 +0100
@@ -14,6 +14,15 @@
 #[derive(Debug, Deserialize, Clone, Default)]
 pub struct EmptyResponse {}
 
+/// Result of a method that can (but doesn't always) download data.
+#[derive(Debug)]
+pub enum DownloadResponse<T: DeserializeOwned + std::fmt::Debug> {
+    /// Downloaded data has been written to the supplied Writer.
+    Downloaded,
+    /// A structured response has been returned.
+    Response(T),
+}
+
 /// The Content-Type header is set automatically to application/json.
 pub async fn do_request<
     Req: Serialize + std::fmt::Debug,
@@ -136,14 +145,14 @@
     }
 }
 
-pub async fn do_download<Req: Serialize + std::fmt::Debug>(
+pub async fn do_download<Req: Serialize + std::fmt::Debug, Resp: DeserializeOwned + std::fmt::Debug>(
     cl: &TlsClient,
     path: &str,
     headers: &[(hyper::header::HeaderName, String)],
     http_method: &str,
     rq: Option<Req>,
-    dst: &mut dyn std::io::Write,
-) -> Result<()> {
+    dst: Option<&mut dyn std::io::Write>,
+) -> Result<DownloadResponse<Resp>> {
     let mut path = path.to_string();
     let mut http_response;
     let mut i = 0;
@@ -201,19 +210,38 @@
         }
     }
 
+    let headers = http_response.as_ref().unwrap().headers();
+    if let Some(ct) = headers.get(hyper::header::CONTENT_TYPE) {
+        if ct.to_str()?.contains("application/json") {
+            let status = http_response.as_ref().unwrap().status();
+            let response_body = hyper::body::to_bytes(http_response.unwrap().into_body()).await?;
+
+            return if !status.is_success() {
+                Err(ApiError::HTTPResponseError(status, body_to_str(response_body)).into())
+            } else {
+                serde_json::from_reader(response_body.as_ref())
+                    .map_err(|e| anyhow::Error::from(e).context(body_to_str(response_body)))
+                    .map(DownloadResponse::Response)
+            }
+        }
+    }
     let response_body = http_response.unwrap().into_body();
-    let write_results = response_body
-        .map(move |chunk| {
-            dst.write(chunk?.as_ref())
-                .map(|_| ())
-                .map_err(anyhow::Error::from)
-        })
-        .collect::<Vec<Result<()>>>()
-        .await;
-    if let Some(e) = write_results.into_iter().find(|r| r.is_err()) {
-        return e;
+    if let Some(dst) = dst {
+        let write_results = response_body
+            .map(move |chunk| {
+                dst.write(chunk?.as_ref())
+                    .map(|_| ())
+                    .map_err(anyhow::Error::from)
+            })
+            .collect::<Vec<Result<()>>>()
+            .await;
+        if let Some(e) = write_results.into_iter().find(|r| r.is_err()) {
+            return Err(e.unwrap_err());
+        }
+        Ok(DownloadResponse::Downloaded)
+    } else {
+        Err(ApiError::DataAvailableError("do_download: No destination for downloaded data was specified".into()).into())
     }
-    Ok(())
 }
 
 /// A resumable upload in progress, useful for sending large objects.