changeset 81:171be899018e

Improve error reporting
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 24 Oct 2020 11:16:52 +0200
parents c2efbeae7103
children 9bffd4cf3e50
files async-google-apis-common/Cargo.lock async-google-apis-common/src/error.rs async-google-apis-common/src/http.rs async-google-apis-common/src/lib.rs async-google-apis-common/src/multipart.rs
diffstat 5 files changed, 101 insertions(+), 174 deletions(-) [+]
line wrap: on
line diff
--- a/async-google-apis-common/Cargo.lock	Sat Oct 24 11:12:10 2020 +0200
+++ b/async-google-apis-common/Cargo.lock	Sat Oct 24 11:16:52 2020 +0200
@@ -14,18 +14,18 @@
 
 [[package]]
 name = "async-google-apis-common"
-version = "0.1.2"
+version = "0.1.3"
 dependencies = [
  "anyhow",
  "chrono",
  "hyper",
- "hyper-rustls 0.20.0",
+ "hyper-rustls",
  "log",
  "percent-encoding",
  "radix64",
  "serde",
  "serde_json",
- "tokio 0.3.0",
+ "tokio",
  "yup-oauth2",
 ]
 
@@ -117,15 +117,6 @@
 ]
 
 [[package]]
-name = "ct-logs"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c8e13110a84b6315df212c045be706af261fd364791cad863285439ebba672e"
-dependencies = [
- "sct",
-]
-
-[[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -256,7 +247,7 @@
  "http",
  "indexmap",
  "slab",
- "tokio 0.2.22",
+ "tokio",
  "tokio-util",
  "tracing",
 ]
@@ -318,7 +309,7 @@
  "itoa",
  "pin-project",
  "socket2",
- "tokio 0.2.22",
+ "tokio",
  "tower-service",
  "tracing",
  "want",
@@ -331,32 +322,14 @@
 checksum = "ac965ea399ec3a25ac7d13b8affd4b8f39325cca00858ddf5eb29b79e6b14b08"
 dependencies = [
  "bytes",
- "ct-logs 0.6.0",
+ "ct-logs",
  "futures-util",
  "hyper",
  "log",
- "rustls 0.17.0",
- "rustls-native-certs 0.3.0",
- "tokio 0.2.22",
- "tokio-rustls 0.13.1",
- "webpki",
-]
-
-[[package]]
-name = "hyper-rustls"
-version = "0.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6"
-dependencies = [
- "bytes",
- "ct-logs 0.7.0",
- "futures-util",
- "hyper",
- "log",
- "rustls 0.18.1",
- "rustls-native-certs 0.4.0",
- "tokio 0.2.22",
- "tokio-rustls 0.14.1",
+ "rustls",
+ "rustls-native-certs",
+ "tokio",
+ "tokio-rustls",
  "webpki",
 ]
 
@@ -634,40 +607,15 @@
 ]
 
 [[package]]
-name = "rustls"
-version = "0.18.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
-dependencies = [
- "base64 0.12.3",
- "log",
- "ring",
- "sct",
- "webpki",
-]
-
-[[package]]
 name = "rustls-native-certs"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75ffeb84a6bd9d014713119542ce415db3a3e4748f0bfce1e1416cd224a23a5"
 dependencies = [
  "openssl-probe",
- "rustls 0.17.0",
+ "rustls",
  "schannel",
- "security-framework 0.4.4",
-]
-
-[[package]]
-name = "rustls-native-certs"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "629d439a7672da82dd955498445e496ee2096fe2117b9f796558a43fdb9e59b8"
-dependencies = [
- "openssl-probe",
- "rustls 0.18.1",
- "schannel",
- "security-framework 1.0.0",
+ "security-framework",
 ]
 
 [[package]]
@@ -712,20 +660,7 @@
  "core-foundation",
  "core-foundation-sys",
  "libc",
- "security-framework-sys 0.4.3",
-]
-
-[[package]]
-name = "security-framework"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad502866817f0575705bd7be36e2b2535cc33262d493aa733a2ec862baa2bc2b"
-dependencies = [
- "bitflags",
- "core-foundation",
- "core-foundation-sys",
- "libc",
- "security-framework-sys 1.0.0",
+ "security-framework-sys",
 ]
 
 [[package]]
@@ -739,16 +674,6 @@
 ]
 
 [[package]]
-name = "security-framework-sys"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51ceb04988b17b6d1dcd555390fa822ca5637b4a14e1f5099f13d351bed4d6c7"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
 name = "serde"
 version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -846,28 +771,14 @@
  "mio",
  "pin-project-lite",
  "slab",
-]
-
-[[package]]
-name = "tokio"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7137dbb0abee577362ccdc7df21605cfcbb949243aeab47dac9ea6ef7d830e21"
-dependencies = [
- "bytes",
- "fnv",
- "futures-core",
- "memchr",
- "pin-project-lite",
- "slab",
  "tokio-macros",
 ]
 
 [[package]]
 name = "tokio-macros"
-version = "0.3.0"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d48caa7b66c7a6ec943edf78d21a594fbeb24e536c781da67d5c32edec54103f"
+checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -881,20 +792,8 @@
 checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4"
 dependencies = [
  "futures-core",
- "rustls 0.17.0",
- "tokio 0.2.22",
- "webpki",
-]
-
-[[package]]
-name = "tokio-rustls"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
-dependencies = [
- "futures-core",
- "rustls 0.18.1",
- "tokio 0.2.22",
+ "rustls",
+ "tokio",
  "webpki",
 ]
 
@@ -909,7 +808,7 @@
  "futures-sink",
  "log",
  "pin-project-lite",
- "tokio 0.2.22",
+ "tokio",
 ]
 
 [[package]]
@@ -1122,22 +1021,22 @@
 
 [[package]]
 name = "yup-oauth2"
-version = "5.0.0"
+version = "4.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634867fbb0b01d365fdb5f244b54bdc248d96ac18372f76bc97bfbd2ae250bf2"
+checksum = "749192b9464694a95dbaf0586e845c835b315e38d491aa2766a8477aaadb48ec"
 dependencies = [
  "base64 0.12.3",
  "chrono",
  "futures",
  "http",
  "hyper",
- "hyper-rustls 0.21.0",
+ "hyper-rustls",
  "log",
  "percent-encoding",
- "rustls 0.17.0",
+ "rustls",
  "seahash",
  "serde",
  "serde_json",
- "tokio 0.3.0",
+ "tokio",
  "url",
 ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/async-google-apis-common/src/error.rs	Sat Oct 24 11:16:52 2020 +0200
@@ -0,0 +1,13 @@
+#[derive(Debug)]
+pub enum ApiError {
+    HTTPResponseError(hyper::StatusCode, String),
+    RedirectError(String),
+    InputDataError(String),
+}
+
+impl std::error::Error for ApiError {}
+impl std::fmt::Display for ApiError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Debug::fmt(self, f)
+    }
+}
--- a/async-google-apis-common/src/http.rs	Sat Oct 24 11:12:10 2020 +0200
+++ b/async-google-apis-common/src/http.rs	Sat Oct 24 11:16:52 2020 +0200
@@ -1,11 +1,17 @@
 use crate::*;
 
+use anyhow::Context;
+
+fn body_to_str(b: hyper::body::Bytes) -> String {
+    String::from_utf8(b.to_vec()).unwrap_or("[UTF-8 decode failed]".into())
+}
+
 /// This type is used as type parameter to the following functions, when `rq` is `None`.
-#[derive(Serialize)]
+#[derive(Debug, Serialize)]
 pub struct EmptyRequest {}
 
 /// The Content-Type header is set automatically to application/json.
-pub async fn do_request<Req: Serialize, Resp: DeserializeOwned + Clone>(
+pub async fn do_request<Req: Serialize + std::fmt::Debug, Resp: DeserializeOwned + Clone>(
     cl: &TlsClient,
     path: &str,
     headers: &[(String, String)],
@@ -19,7 +25,7 @@
     reqb = reqb.header("Content-Type", "application/json");
     let body_str;
     if let Some(rq) = rq {
-        body_str = serde_json::to_string(&rq)?;
+        body_str = serde_json::to_string(&rq).context(format!("{:?}", rq))?;
     } else {
         body_str = "".to_string();
     }
@@ -38,22 +44,23 @@
     let http_response = cl.request(http_request).await?;
     let status = http_response.status();
 
-    debug!("do_request: HTTP response with status {} received: {:?}", status, http_response);
+    debug!(
+        "do_request: HTTP response with status {} received: {:?}",
+        status, http_response
+    );
 
     let response_body = hyper::body::to_bytes(http_response.into_body()).await?;
-    let response_body_str = String::from_utf8(response_body.to_vec());
     if !status.is_success() {
-        Err(Error::new(ApiError::HTTPError(
-            status,
-            response_body_str.unwrap_or("".to_string()),
-        )))
+        Err(ApiError::HTTPResponseError(status, body_to_str(response_body)).into())
     } else {
-        serde_json::from_reader(response_body.as_ref()).map_err(Error::from)
+        // Evaluate body_to_str lazily
+        serde_json::from_reader(response_body.as_ref())
+            .map_err(|e| anyhow::Error::from(e).context(body_to_str(response_body)))
     }
 }
 
 /// The Content-Length header is set automatically.
-pub async fn do_upload_multipart<Req: Serialize, Resp: DeserializeOwned + Clone>(
+pub async fn do_upload_multipart<Req: Serialize + std::fmt::Debug, Resp: DeserializeOwned + Clone>(
     cl: &TlsClient,
     path: &str,
     headers: &[(String, String)],
@@ -75,24 +82,27 @@
 
     let body = hyper::Body::from(data.as_ref().to_vec());
     let http_request = reqb.body(body)?;
-    debug!("do_upload_multipart: Launching HTTP request: {:?}", http_request);
+    debug!(
+        "do_upload_multipart: Launching HTTP request: {:?}",
+        http_request
+    );
     let http_response = cl.request(http_request).await?;
     let status = http_response.status();
-    debug!("do_upload_multipart: HTTP response with status {} received: {:?}", status, http_response);
+    debug!(
+        "do_upload_multipart: HTTP response with status {} received: {:?}",
+        status, http_response
+    );
     let response_body = hyper::body::to_bytes(http_response.into_body()).await?;
-    let response_body_str = String::from_utf8(response_body.to_vec());
 
     if !status.is_success() {
-        Err(Error::new(ApiError::HTTPError(
-            status,
-            response_body_str.unwrap_or("".to_string()),
-        )))
+        Err(ApiError::HTTPResponseError(status, body_to_str(response_body)).into())
     } else {
-        serde_json::from_reader(response_body.as_ref()).map_err(Error::from)
+        serde_json::from_reader(response_body.as_ref())
+            .map_err(|e| anyhow::Error::from(e).context(body_to_str(response_body)))
     }
 }
 
-pub async fn do_download<Req: Serialize>(
+pub async fn do_download<Req: Serialize + std::fmt::Debug>(
     cl: &TlsClient,
     path: &str,
     headers: &[(String, String)],
@@ -110,7 +120,7 @@
         for (k, v) in headers {
             reqb = reqb.header(k, v);
         }
-        let body_str = serde_json::to_string(&rq)?;
+        let body_str = serde_json::to_string(&rq).context(format!("{:?}", rq))?;
         let body;
         if body_str == "null" {
             body = hyper::Body::from("");
@@ -119,11 +129,17 @@
         }
 
         let http_request = reqb.body(body)?;
-        debug!("do_download: Redirect {}, Launching HTTP request: {:?}", i, http_request);
+        debug!(
+            "do_download: Redirect {}, Launching HTTP request: {:?}",
+            i, http_request
+        );
 
         http_response = Some(cl.request(http_request).await?);
         let status = http_response.as_ref().unwrap().status();
-        debug!("do_download: Redirect {}, HTTP response with status {} received: {:?}", i, status, http_response);
+        debug!(
+            "do_download: Redirect {}, HTTP response with status {} received: {:?}",
+            i, status, http_response
+        );
 
         if status.is_success() {
             break;
@@ -135,21 +151,29 @@
                 .headers()
                 .get(hyper::header::LOCATION);
             if new_location.is_none() {
-                return Err(Error::new(ApiError::HTTPError(
-                    status,
-                    format!("Redirect doesn't contain a Location: header"),
-                )));
+                return Err(ApiError::RedirectError(format!(
+                    "Redirect doesn't contain a Location: header"
+                ))
+                .into());
             }
             path = new_location.unwrap().to_str()?.to_string();
             continue;
         } else if !status.is_success() {
-            return Err(Error::new(ApiError::HTTPError(status, String::new())));
+            return Err(ApiError::HTTPResponseError(
+                status,
+                body_to_str(hyper::body::to_bytes(http_response.unwrap().into_body()).await?),
+            )
+            .into());
         }
     }
 
     let response_body = http_response.unwrap().into_body();
     let write_results = response_body
-        .map(move |chunk| dst.write(chunk?.as_ref()).map(|_| ()).map_err(Error::from))
+        .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()) {
--- a/async-google-apis-common/src/lib.rs	Sat Oct 24 11:12:10 2020 +0200
+++ b/async-google-apis-common/src/lib.rs	Sat Oct 24 11:16:52 2020 +0200
@@ -1,11 +1,18 @@
 //! Common types, imports, and functions used by generated code, including HTTP requests and error
 //! types.
 
+mod error;
+pub use error::*;
+mod http;
+pub use http::*;
+
+mod multipart;
+
 pub use hyper;
+pub use log::{debug, error, info, trace, warn};
 pub use serde;
 pub use serde_json;
 pub use yup_oauth2;
-pub use log::{trace, debug, info, warn, error};
 
 pub use anyhow::{Error, Result};
 pub use chrono::{DateTime, Utc};
@@ -17,21 +24,3 @@
 pub type Authenticator = yup_oauth2::authenticator::Authenticator<TlsConnr>;
 pub type TlsClient = hyper::Client<TlsConnr, hyper::Body>;
 pub type TlsConnr = hyper_rustls::HttpsConnector<hyper::client::HttpConnector>;
-
-#[derive(Debug, Clone)]
-pub enum ApiError {
-    InputDataError(String),
-    HTTPError(hyper::StatusCode, String),
-}
-
-impl std::error::Error for ApiError {}
-impl std::fmt::Display for ApiError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Debug::fmt(self, f)
-    }
-}
-
-mod multipart;
-mod http;
-pub use http::*;
-
--- a/async-google-apis-common/src/multipart.rs	Sat Oct 24 11:12:10 2020 +0200
+++ b/async-google-apis-common/src/multipart.rs	Sat Oct 24 11:16:52 2020 +0200
@@ -5,10 +5,12 @@
 use serde::Serialize;
 use std::io::Write;
 
+use anyhow::Context;
+
 pub const MIME_BOUNDARY: &'static str = "PB0BHe6XN3O6Q4bpnWQgS1pKfMfglTZdifFvh8YIc2APj4Cz3C";
 
-pub fn format_multipart<Req: Serialize>(req: &Req, data: Bytes) -> anyhow::Result<Bytes> {
-    let meta = serde_json::to_string(req)?;
+pub fn format_multipart<Req: Serialize + std::fmt::Debug>(req: &Req, data: Bytes) -> anyhow::Result<Bytes> {
+    let meta = serde_json::to_string(req).context(format!("{:?}", req))?;
     let mut buf = Vec::with_capacity(meta.len() + (1.5 * (data.len() as f64)) as usize);
 
     // Write metadata.