changeset 122:779081e4efe2

gcs_example: Enable more features and encode mandatory parameters
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 25 Oct 2020 18:22:02 +0100
parents f14c93b7ab21
children 907bfb45511b
files async-google-apis-common/src/http.rs drive_example/README.md gcs_example/README.md gcs_example/src/main.rs gcs_example/src/storage_v1_types.rs generate/generate.py generate/templates.py
diffstat 7 files changed, 239 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/async-google-apis-common/src/http.rs	Sun Oct 25 15:08:03 2020 +0100
+++ b/async-google-apis-common/src/http.rs	Sun Oct 25 18:22:02 2020 +0100
@@ -145,13 +145,16 @@
     }
 }
 
-pub async fn do_download<Req: Serialize + std::fmt::Debug, Resp: DeserializeOwned + 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: Option<&mut dyn std::io::Write>,
+    dst: Option<&mut (dyn tokio::io::AsyncWrite + std::marker::Unpin)>,
 ) -> Result<DownloadResponse<Resp>> {
     let mut path = path.to_string();
     let mut http_response;
@@ -222,25 +225,22 @@
                 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();
+
+    use tokio::io::AsyncWriteExt;
+    let mut response_body = http_response.unwrap().into_body();
     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());
+        while let Some(chunk) = tokio::stream::StreamExt::next(&mut response_body).await {
+            dst.write(chunk?.as_ref()).await?;
         }
         Ok(DownloadResponse::Downloaded)
     } else {
-        Err(ApiError::DataAvailableError("do_download: No destination for downloaded data was specified".into()).into())
+        Err(ApiError::DataAvailableError(
+            "do_download: No destination for downloaded data was specified".into(),
+        )
+        .into())
     }
 }
 
@@ -286,8 +286,11 @@
         }
     }
     pub fn set_max_chunksize(&mut self, size: usize) -> Result<&mut Self> {
-        if size % (1024*256) != 0 {
-            Err(ApiError::InputDataError("ResumableUpload: max_chunksize must be multiple of 256 KiB.".into()).into())
+        if size % (1024 * 256) != 0 {
+            Err(ApiError::InputDataError(
+                "ResumableUpload: max_chunksize must be multiple of 256 KiB.".into(),
+            )
+            .into())
         } else {
             self.max_chunksize = size;
             Ok(self)
--- a/drive_example/README.md	Sun Oct 25 15:08:03 2020 +0100
+++ b/drive_example/README.md	Sun Oct 25 18:22:02 2020 +0100
@@ -16,3 +16,6 @@
 [Developer Console](https://console.developers.google.com) and place it into the
 file `client_secret.json` in your working directory so that `drive_example` can
 find it.
+
+Run with `RUST_LOG=debug` in order to see an accurate record of HTTP requests
+being sent and received.
--- a/gcs_example/README.md	Sun Oct 25 15:08:03 2020 +0100
+++ b/gcs_example/README.md	Sun Oct 25 18:22:02 2020 +0100
@@ -29,3 +29,6 @@
 ```bash
 $ gcs_example --help
 ```
+
+Run with `RUST_LOG=debug` in order to see an accurate record of HTTP requests
+being sent and received.
--- a/gcs_example/src/main.rs	Sun Oct 25 15:08:03 2020 +0100
+++ b/gcs_example/src/main.rs	Sun Oct 25 18:22:02 2020 +0100
@@ -20,7 +20,7 @@
 ) -> common::Result<()> {
     let mut params = storage_v1_types::ObjectsInsertParams::default();
     params.bucket = bucket.into();
-    params.name = Some("test_directory/".to_string() + p.file_name().unwrap().to_str().unwrap());
+    params.name = Some(p.file_name().unwrap().to_str().unwrap().into());
     let obj = storage_v1_types::Object::default();
 
     let f = tokio::fs::OpenOptions::new().read(true).open(p).await?;
@@ -36,6 +36,32 @@
     Ok(())
 }
 
+async fn download_file(
+    mut cl: storage_v1_types::ObjectsService,
+    bucket: &str,
+    id: &str,
+) -> common::Result<()> {
+    // Set alt=media for download.
+    let mut gparams = storage_v1_types::StorageParams::default();
+    gparams.alt = Some("media".into());
+    let mut params = storage_v1_types::ObjectsGetParams::default();
+    params.storage_params = Some(gparams);
+    params.bucket = bucket.into();
+    params.object = id.into();
+
+    let id = id.replace("/", "_");
+    let mut f = tokio::fs::OpenOptions::new()
+        .write(true)
+        .create(true)
+        .open(id)
+        .await?;
+    let result = cl.get(&params, Some(&mut f)).await?;
+
+    println!("Downloaded object: {:?}", result);
+
+    Ok(())
+}
+
 #[tokio::main]
 async fn main() {
     env_logger::init();
@@ -45,17 +71,26 @@
         .about("Upload objects to GCS.")
         .arg(
             clap::Arg::with_name("BUCKET")
+                .help("target bucket")
                 .long("bucket")
-                .help("target bucket")
+                .required(true)
+                .short("b")
                 .takes_value(true),
         )
         .arg(
-            clap::Arg::with_name("FILE")
-                .help("File to upload")
+            clap::Arg::with_name("ACTION")
+                .help("What to do.")
+                .long("action")
+                .possible_values(&["get", "list", "put"])
                 .required(true)
-                .index(1)
+                .short("a")
                 .takes_value(true),
         )
+        .arg(
+            clap::Arg::with_name("FILE_OR_OBJECT")
+                .help("File to upload")
+                .index(1),
+        )
         .get_matches();
 
     let https_client = https_client();
@@ -69,16 +104,29 @@
             .build()
             .await
             .expect("ServiceAccount authenticator failed.");
+    let authenticator = std::rc::Rc::new(authenticator);
 
-    let cl = storage_v1_types::ObjectsService::new(https_client, std::rc::Rc::new(authenticator));
+    let action = matches.value_of("ACTION").expect("--action is required.");
+    let buck = matches
+        .value_of("BUCKET")
+        .expect("--bucket is a mandatory argument.");
 
-    if let Some(fp) = matches.value_of("FILE") {
-        if let Some(buck) = matches.value_of("BUCKET") {
-            upload_file(cl, buck, Path::new(&fp))
-                .await
-                .expect("Upload failed :(");
-            return;
-        }
+    if action == "get" {
+        let obj = matches
+            .value_of("FILE_OR_OBJECT")
+            .expect("OBJECT is a mandatory argument.");
+        let cl = storage_v1_types::ObjectsService::new(https_client, authenticator);
+        download_file(cl, buck, obj)
+            .await
+            .expect("Download failed :(");
+    } else if action == "put" {
+        let fp = matches
+            .value_of("FILE_OR_OBJECT")
+            .expect("FILE is a mandatory argument.");
+        let cl = storage_v1_types::ObjectsService::new(https_client, authenticator);
+        upload_file(cl, buck, Path::new(&fp))
+            .await
+            .expect("Upload failed :(");
+        return;
     }
-    println!("Please specify file to upload as first argument.");
 }
--- a/gcs_example/src/storage_v1_types.rs	Sun Oct 25 15:08:03 2020 +0100
+++ b/gcs_example/src/storage_v1_types.rs	Sun Oct 25 18:22:02 2020 +0100
@@ -4426,8 +4426,8 @@
     pub async fn delete(&mut self, params: &BucketAccessControlsDeleteParams) -> Result<()> {
         let rel_path = format!(
             "b/{bucket}/acl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -4465,8 +4465,8 @@
     ) -> Result<BucketAccessControl> {
         let rel_path = format!(
             "b/{bucket}/acl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -4503,7 +4503,10 @@
         params: &BucketAccessControlsInsertParams,
         req: &BucketAccessControl,
     ) -> Result<BucketAccessControl> {
-        let rel_path = format!("b/{bucket}/acl", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/acl",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4539,7 +4542,10 @@
         &mut self,
         params: &BucketAccessControlsListParams,
     ) -> Result<BucketAccessControls> {
-        let rel_path = format!("b/{bucket}/acl", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/acl",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4577,8 +4583,8 @@
     ) -> Result<BucketAccessControl> {
         let rel_path = format!(
             "b/{bucket}/acl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -4618,8 +4624,8 @@
     ) -> Result<BucketAccessControl> {
         let rel_path = format!(
             "b/{bucket}/acl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -4688,7 +4694,10 @@
 
     /// Permanently deletes an empty bucket.
     pub async fn delete(&mut self, params: &BucketsDeleteParams) -> Result<()> {
-        let rel_path = format!("b/{bucket}", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4720,7 +4729,10 @@
 
     /// Returns metadata for the specified bucket.
     pub async fn get(&mut self, params: &BucketsGetParams) -> Result<Bucket> {
-        let rel_path = format!("b/{bucket}", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4752,7 +4764,10 @@
 
     /// Returns an IAM policy for the specified bucket.
     pub async fn get_iam_policy(&mut self, params: &BucketsGetIamPolicyParams) -> Result<Policy> {
-        let rel_path = format!("b/{bucket}/iam", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/iam",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4852,7 +4867,10 @@
         &mut self,
         params: &BucketsLockRetentionPolicyParams,
     ) -> Result<Bucket> {
-        let rel_path = format!("b/{bucket}/lockRetentionPolicy", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/lockRetentionPolicy",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4884,7 +4902,10 @@
 
     /// Patches a bucket. Changes to the bucket will be readable immediately after writing, but configuration changes may take time to propagate.
     pub async fn patch(&mut self, params: &BucketsPatchParams, req: &Bucket) -> Result<Bucket> {
-        let rel_path = format!("b/{bucket}", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4921,7 +4942,10 @@
         params: &BucketsSetIamPolicyParams,
         req: &Policy,
     ) -> Result<Policy> {
-        let rel_path = format!("b/{bucket}/iam", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/iam",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4957,7 +4981,10 @@
         &mut self,
         params: &BucketsTestIamPermissionsParams,
     ) -> Result<TestIamPermissionsResponse> {
-        let rel_path = format!("b/{bucket}/iam/testPermissions", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/iam/testPermissions",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -4989,7 +5016,10 @@
 
     /// Updates a bucket. Changes to the bucket will be readable immediately after writing, but configuration changes may take time to propagate.
     pub async fn update(&mut self, params: &BucketsUpdateParams, req: &Bucket) -> Result<Bucket> {
-        let rel_path = format!("b/{bucket}", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -5127,8 +5157,8 @@
     pub async fn delete(&mut self, params: &DefaultObjectAccessControlsDeleteParams) -> Result<()> {
         let rel_path = format!(
             "b/{bucket}/defaultObjectAcl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5166,8 +5196,8 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/defaultObjectAcl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5204,7 +5234,10 @@
         params: &DefaultObjectAccessControlsInsertParams,
         req: &ObjectAccessControl,
     ) -> Result<ObjectAccessControl> {
-        let rel_path = format!("b/{bucket}/defaultObjectAcl", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/defaultObjectAcl",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -5240,7 +5273,10 @@
         &mut self,
         params: &DefaultObjectAccessControlsListParams,
     ) -> Result<ObjectAccessControls> {
-        let rel_path = format!("b/{bucket}/defaultObjectAcl", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/defaultObjectAcl",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -5278,8 +5314,8 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/defaultObjectAcl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5319,8 +5355,8 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/defaultObjectAcl/{entity}",
-            bucket = params.bucket,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5391,8 +5427,8 @@
     pub async fn delete(&mut self, params: &NotificationsDeleteParams) -> Result<()> {
         let rel_path = format!(
             "b/{bucket}/notificationConfigs/{notification}",
-            bucket = params.bucket,
-            notification = params.notification
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            notification = percent_encode(params.notification.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5427,8 +5463,8 @@
     pub async fn get(&mut self, params: &NotificationsGetParams) -> Result<Notification> {
         let rel_path = format!(
             "b/{bucket}/notificationConfigs/{notification}",
-            bucket = params.bucket,
-            notification = params.notification
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            notification = percent_encode(params.notification.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5465,7 +5501,10 @@
         params: &NotificationsInsertParams,
         req: &Notification,
     ) -> Result<Notification> {
-        let rel_path = format!("b/{bucket}/notificationConfigs", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/notificationConfigs",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -5498,7 +5537,10 @@
 
     /// Retrieves a list of notification subscriptions for a given bucket.
     pub async fn list(&mut self, params: &NotificationsListParams) -> Result<Notifications> {
-        let rel_path = format!("b/{bucket}/notificationConfigs", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/notificationConfigs",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -5567,9 +5609,9 @@
     pub async fn delete(&mut self, params: &ObjectAccessControlsDeleteParams) -> Result<()> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/acl/{entity}",
-            bucket = params.bucket,
-            object = params.object,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5607,9 +5649,9 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/acl/{entity}",
-            bucket = params.bucket,
-            object = params.object,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5648,8 +5690,8 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/acl",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5688,8 +5730,8 @@
     ) -> Result<ObjectAccessControls> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/acl",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5728,9 +5770,9 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/acl/{entity}",
-            bucket = params.bucket,
-            object = params.object,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5770,9 +5812,9 @@
     ) -> Result<ObjectAccessControl> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/acl/{entity}",
-            bucket = params.bucket,
-            object = params.object,
-            entity = params.entity
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC),
+            entity = percent_encode(params.entity.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5847,8 +5889,10 @@
     ) -> Result<Object> {
         let rel_path = format!(
             "b/{destinationBucket}/o/{destinationObject}/compose",
-            destinationBucket = params.destination_bucket,
-            destinationObject = params.destination_object
+            destinationBucket =
+                percent_encode(params.destination_bucket.as_bytes(), NON_ALPHANUMERIC),
+            destinationObject =
+                percent_encode(params.destination_object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5884,10 +5928,12 @@
     pub async fn copy(&mut self, params: &ObjectsCopyParams, req: &Object) -> Result<Object> {
         let rel_path = format!(
             "b/{sourceBucket}/o/{sourceObject}/copyTo/b/{destinationBucket}/o/{destinationObject}",
-            sourceBucket = params.source_bucket,
-            sourceObject = params.source_object,
-            destinationBucket = params.destination_bucket,
-            destinationObject = params.destination_object
+            sourceBucket = percent_encode(params.source_bucket.as_bytes(), NON_ALPHANUMERIC),
+            sourceObject = percent_encode(params.source_object.as_bytes(), NON_ALPHANUMERIC),
+            destinationBucket =
+                percent_encode(params.destination_bucket.as_bytes(), NON_ALPHANUMERIC),
+            destinationObject =
+                percent_encode(params.destination_object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5923,8 +5969,8 @@
     pub async fn delete(&mut self, params: &ObjectsDeleteParams) -> Result<()> {
         let rel_path = format!(
             "b/{bucket}/o/{object}",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -5964,12 +6010,12 @@
     pub async fn get(
         &mut self,
         params: &ObjectsGetParams,
-        dst: Option<&mut dyn std::io::Write>,
+        dst: Option<&mut (dyn tokio::io::AsyncWrite + std::marker::Unpin)>,
     ) -> Result<DownloadResponse<Object>> {
         let rel_path = format!(
             "b/{bucket}/o/{object}",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
 
@@ -6006,8 +6052,8 @@
     pub async fn get_iam_policy(&mut self, params: &ObjectsGetIamPolicyParams) -> Result<Policy> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/iam",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6040,7 +6086,10 @@
 
     /// Stores a new object and metadata.
     pub async fn insert(&mut self, params: &ObjectsInsertParams, req: &Object) -> Result<Object> {
-        let rel_path = format!("b/{bucket}/o", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/o",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -6080,7 +6129,10 @@
         req: &Object,
         data: hyper::body::Bytes,
     ) -> Result<Object> {
-        let rel_path = format!("/upload/storage/v1/b/{bucket}/o", bucket = params.bucket);
+        let rel_path = format!(
+            "/upload/storage/v1/b/{bucket}/o",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/".to_string() + &rel_path;
 
         let tok;
@@ -6127,7 +6179,7 @@
     ) -> Result<ResumableUpload<'client, Object>> {
         let rel_path = format!(
             "/resumable/upload/storage/v1/b/{bucket}/o",
-            bucket = params.bucket
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/".to_string() + &rel_path;
         let tok;
@@ -6175,7 +6227,10 @@
 
     /// Retrieves a list of objects matching the criteria.
     pub async fn list(&mut self, params: &ObjectsListParams) -> Result<Objects> {
-        let rel_path = format!("b/{bucket}/o", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/o",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -6209,8 +6264,8 @@
     pub async fn patch(&mut self, params: &ObjectsPatchParams, req: &Object) -> Result<Object> {
         let rel_path = format!(
             "b/{bucket}/o/{object}",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6248,7 +6303,7 @@
         params: &ObjectsRewriteParams,
         req: &Object,
     ) -> Result<RewriteResponse> {
-        let rel_path = format!("b/{sourceBucket}/o/{sourceObject}/rewriteTo/b/{destinationBucket}/o/{destinationObject}", sourceBucket=params.source_bucket,sourceObject=params.source_object,destinationBucket=params.destination_bucket,destinationObject=params.destination_object);
+        let rel_path = format!("b/{sourceBucket}/o/{sourceObject}/rewriteTo/b/{destinationBucket}/o/{destinationObject}", sourceBucket=percent_encode(params.source_bucket.as_bytes(), NON_ALPHANUMERIC),sourceObject=percent_encode(params.source_object.as_bytes(), NON_ALPHANUMERIC),destinationBucket=percent_encode(params.destination_bucket.as_bytes(), NON_ALPHANUMERIC),destinationObject=percent_encode(params.destination_object.as_bytes(), NON_ALPHANUMERIC));
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -6287,8 +6342,8 @@
     ) -> Result<Policy> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/iam",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6327,8 +6382,8 @@
     ) -> Result<TestIamPermissionsResponse> {
         let rel_path = format!(
             "b/{bucket}/o/{object}/iam/testPermissions",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6363,8 +6418,8 @@
     pub async fn update(&mut self, params: &ObjectsUpdateParams, req: &Object) -> Result<Object> {
         let rel_path = format!(
             "b/{bucket}/o/{object}",
-            bucket = params.bucket,
-            object = params.object
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC),
+            object = percent_encode(params.object.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6402,7 +6457,10 @@
         params: &ObjectsWatchAllParams,
         req: &Channel,
     ) -> Result<Channel> {
-        let rel_path = format!("b/{bucket}/o/watch", bucket = params.bucket);
+        let rel_path = format!(
+            "b/{bucket}/o/watch",
+            bucket = percent_encode(params.bucket.as_bytes(), NON_ALPHANUMERIC)
+        );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
         if self.scopes.is_empty() {
@@ -6507,7 +6565,7 @@
     pub async fn create(&mut self, params: &ProjectsHmacKeysCreateParams) -> Result<HmacKey> {
         let rel_path = format!(
             "projects/{projectId}/hmacKeys",
-            projectId = params.project_id
+            projectId = percent_encode(params.project_id.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6542,8 +6600,8 @@
     pub async fn delete(&mut self, params: &ProjectsHmacKeysDeleteParams) -> Result<()> {
         let rel_path = format!(
             "projects/{projectId}/hmacKeys/{accessId}",
-            projectId = params.project_id,
-            accessId = params.access_id
+            projectId = percent_encode(params.project_id.as_bytes(), NON_ALPHANUMERIC),
+            accessId = percent_encode(params.access_id.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6578,8 +6636,8 @@
     pub async fn get(&mut self, params: &ProjectsHmacKeysGetParams) -> Result<HmacKeyMetadata> {
         let rel_path = format!(
             "projects/{projectId}/hmacKeys/{accessId}",
-            projectId = params.project_id,
-            accessId = params.access_id
+            projectId = percent_encode(params.project_id.as_bytes(), NON_ALPHANUMERIC),
+            accessId = percent_encode(params.access_id.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6614,7 +6672,7 @@
     pub async fn list(&mut self, params: &ProjectsHmacKeysListParams) -> Result<HmacKeysMetadata> {
         let rel_path = format!(
             "projects/{projectId}/hmacKeys",
-            projectId = params.project_id
+            projectId = percent_encode(params.project_id.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6653,8 +6711,8 @@
     ) -> Result<HmacKeyMetadata> {
         let rel_path = format!(
             "projects/{projectId}/hmacKeys/{accessId}",
-            projectId = params.project_id,
-            accessId = params.access_id
+            projectId = percent_encode(params.project_id.as_bytes(), NON_ALPHANUMERIC),
+            accessId = percent_encode(params.access_id.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
@@ -6728,7 +6786,7 @@
     ) -> Result<ServiceAccount> {
         let rel_path = format!(
             "projects/{projectId}/serviceAccount",
-            projectId = params.project_id
+            projectId = percent_encode(params.project_id.as_bytes(), NON_ALPHANUMERIC)
         );
         let path = "https://storage.googleapis.com/storage/v1/".to_string() + &rel_path;
         let tok;
--- a/generate/generate.py	Sun Oct 25 15:08:03 2020 +0100
+++ b/generate/generate.py	Sun Oct 25 18:22:02 2020 +0100
@@ -232,13 +232,13 @@
     return frags
 
 
-def resolve_parameters(string, paramsname='params', suffix=''):
+def resolve_parameters(string, paramsname='params'):
     """Returns a Rust syntax for formatting the given string with API
     parameters, and a list of (snake-case) API parameters that are used. """
     pat = re.compile('\{\+?(\w+)\}')
     params = re.findall(pat, string)
     snakeparams = [rust_identifier(p) for p in params]
-    format_params = ','.join(['{}={}.{}{}'.format(p, paramsname, sp, suffix) for (p, sp) in zip(params, snakeparams)])
+    format_params = ','.join(['{}=percent_encode({}.{}.as_bytes(), NON_ALPHANUMERIC)'.format(p, paramsname, sp) for (p, sp) in zip(params, snakeparams)])
     string = string.replace('{+', '{')
     # Some required parameters are in the URL. This rust syntax formats the relative URL part appropriately.
     return 'format!("{}", {})'.format(string, format_params), snakeparams
@@ -276,7 +276,7 @@
             for p, pp in method.get('parameters', {}).items() if ('required' in pp and pp['location'] != 'path')
         }
         # Types of the function
-        in_type = method['request']['$ref'] if 'request' in method else '()'
+        in_type = method['request']['$ref'] if 'request' in method else None
         out_type = method['response']['$ref'] if 'response' in method else '()'
 
         is_download = method.get('supportsMediaDownload', False)
@@ -306,8 +306,7 @@
                 rust_identifier(methodname),
                 'param_type':
                 params_type_name,
-                'in_type':
-                in_type,
+                'in_type': in_type,
                 'out_type':
                 out_type,
                 'base_path':
@@ -332,8 +331,6 @@
                 'http_method':
                 http_method
             }
-            if in_type == '()':
-                data_download.pop('in_type')
             method_fragments.append(chevron.render(DownloadMethodTmpl, data_download))
         else:
             data_normal = {
@@ -341,8 +338,7 @@
                 rust_identifier(methodname),
                 'param_type':
                 params_type_name,
-                'in_type':
-                in_type,
+                'in_type': in_type,
                 'out_type':
                 out_type,
                 'base_path':
@@ -367,8 +363,6 @@
                 'http_method':
                 http_method
             }
-            if in_type == '()':
-                data_normal.pop('in_type')
             method_fragments.append(chevron.render(NormalMethodTmpl, data_normal))
 
         # We generate an additional implementation with the option of uploading data.
--- a/generate/templates.py	Sun Oct 25 15:08:03 2020 +0100
+++ b/generate/templates.py	Sun Oct 25 18:22:02 2020 +0100
@@ -261,7 +261,8 @@
 /// `dst`. If `dst` is `None` despite data being available for download, `ApiError::DataAvailableError`
 /// is returned.
 pub async fn {{{name}}}(
-    &mut self, params: &{{{param_type}}}, {{#in_type}}req: &{{{in_type}}},{{/in_type}} dst: Option<&mut dyn std::io::Write>)
+    &mut self, params: &{{{param_type}}}, {{#in_type}}req: &{{{in_type}}},{{/in_type}}
+    dst: Option<&mut (dyn tokio::io::AsyncWrite + std::marker::Unpin)>)
     -> Result<DownloadResponse<{{out_type}}>> {
 
     let rel_path = {{{rel_path_expr}}};