google_cloud_storage/storage/client.rs
1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::request_options::RequestOptions;
16use crate::Error;
17use crate::builder::storage::ReadObject;
18use crate::builder::storage::WriteObject;
19use crate::read_resume_policy::ReadResumePolicy;
20#[cfg(google_cloud_unstable_storage_bidi)]
21use crate::storage::bidi::OpenObject;
22use crate::storage::common_options::CommonOptions;
23use crate::streaming_source::Payload;
24use auth::credentials::CacheableResource;
25use base64::Engine;
26use base64::prelude::BASE64_STANDARD;
27use gax::client_builder::{Error as BuilderError, Result as BuilderResult};
28use gaxi::options::ClientConfig;
29use gaxi::options::Credentials;
30use http::Extensions;
31use std::sync::Arc;
32
33/// Implements a client for the Cloud Storage API.
34///
35/// # Example
36/// ```
37/// # async fn sample() -> anyhow::Result<()> {
38/// # use google_cloud_storage::client::Storage;
39/// let client = Storage::builder().build().await?;
40/// // use `client` to make requests to Cloud Storage.
41/// # Ok(()) }
42/// ```
43///
44/// # Configuration
45///
46/// To configure `Storage` use the `with_*` methods in the type returned
47/// by [builder()][Storage::builder]. The default configuration should
48/// work for most applications. Common configuration changes include
49///
50/// * [with_endpoint()]: by default this client uses the global default endpoint
51/// (`https://storage.googleapis.com`). Applications using regional
52/// endpoints or running in restricted networks (e.g. a network configured
53/// with [Private Google Access with VPC Service Controls]) may want to
54/// override this default.
55/// * [with_credentials()]: by default this client uses
56/// [Application Default Credentials]. Applications using custom
57/// authentication may need to override this default.
58///
59/// # Pooling and Cloning
60///
61/// `Storage` holds a connection pool internally, it is advised to
62/// create one and then reuse it. You do not need to wrap `Storage` in
63/// an [Rc](std::rc::Rc) or [Arc] to reuse it, because it already uses an `Arc`
64/// internally.
65///
66/// # Service Description
67///
68/// The Cloud Storage API allows applications to read and write data through
69/// the abstractions of buckets and objects. For a description of these
70/// abstractions please see <https://cloud.google.com/storage/docs>.
71///
72/// Resources are named as follows:
73///
74/// - Projects are referred to as they are defined by the Resource Manager API,
75/// using strings like `projects/123456` or `projects/my-string-id`.
76///
77/// - Buckets are named using string names of the form:
78/// `projects/{project}/buckets/{bucket}`
79/// For globally unique buckets, `_` may be substituted for the project.
80///
81/// - Objects are uniquely identified by their name along with the name of the
82/// bucket they belong to, as separate strings in this API. For example:
83/// ```no_rust
84/// bucket = "projects/_/buckets/my-bucket"
85/// object = "my-object/with/a/folder-like/name"
86/// ```
87/// Note that object names can contain `/` characters, which are treated as
88/// any other character (no special directory semantics).
89///
90/// [with_endpoint()]: ClientBuilder::with_endpoint
91/// [with_credentials()]: ClientBuilder::with_credentials
92/// [Private Google Access with VPC Service Controls]: https://cloud.google.com/vpc-service-controls/docs/private-connectivity
93/// [Application Default Credentials]: https://cloud.google.com/docs/authentication#adc
94#[derive(Clone, Debug)]
95pub struct Storage<S = crate::stub::DefaultStorage>
96where
97 S: crate::stub::Storage + 'static,
98{
99 stub: std::sync::Arc<S>,
100 options: RequestOptions,
101}
102
103#[derive(Clone, Debug)]
104pub(crate) struct StorageInner {
105 pub client: reqwest::Client,
106 pub cred: auth::credentials::Credentials,
107 pub endpoint: String,
108 pub options: RequestOptions,
109 #[cfg(google_cloud_unstable_storage_bidi)]
110 pub grpc: gaxi::grpc::Client,
111}
112
113impl Storage {
114 /// Returns a builder for [Storage].
115 ///
116 /// # Example
117 /// ```
118 /// # use google_cloud_storage::client::Storage;
119 /// # async fn sample() -> anyhow::Result<()> {
120 /// let client = Storage::builder().build().await?;
121 /// # Ok(()) }
122 /// ```
123 pub fn builder() -> ClientBuilder {
124 ClientBuilder::new()
125 }
126}
127
128impl<S> Storage<S>
129where
130 S: crate::storage::stub::Storage + 'static,
131{
132 /// Creates a new client from the provided stub.
133 ///
134 /// The most common case for calling this function is in tests mocking the
135 /// client's behavior.
136 pub fn from_stub(stub: S) -> Self
137 where
138 S: super::stub::Storage + 'static,
139 {
140 Self {
141 stub: std::sync::Arc::new(stub),
142 options: RequestOptions::new(),
143 }
144 }
145
146 /// Write an object with data from any data source.
147 ///
148 /// # Example
149 /// ```
150 /// # use google_cloud_storage::client::Storage;
151 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
152 /// let response = client
153 /// .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
154 /// .send_buffered()
155 /// .await?;
156 /// println!("response details={response:?}");
157 /// # Ok(()) }
158 /// ```
159 ///
160 /// # Example
161 /// ```
162 /// # use google_cloud_storage::client::Storage;
163 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
164 /// let response = client
165 /// .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
166 /// .send_unbuffered()
167 /// .await?;
168 /// println!("response details={response:?}");
169 /// # Ok(()) }
170 /// ```
171 ///
172 /// You can use many different types as the payload. For example, a string,
173 /// a [bytes::Bytes], a [tokio::fs::File], or a custom type that implements
174 /// the [StreamingSource] trait.
175 ///
176 /// If your data source also implements [Seek], prefer [send_unbuffered()]
177 /// to start the write. Otherwise use [send_buffered()].
178 ///
179 /// # Parameters
180 /// * `bucket` - the bucket name containing the object. In
181 /// `projects/_/buckets/{bucket_id}` format.
182 /// * `object` - the object name.
183 /// * `payload` - the object data.
184 ///
185 /// [Seek]: crate::streaming_source::Seek
186 /// [StreamingSource]: crate::streaming_source::StreamingSource
187 /// [send_buffered()]: crate::builder::storage::WriteObject::send_buffered
188 /// [send_unbuffered()]: crate::builder::storage::WriteObject::send_unbuffered
189 pub fn write_object<B, O, T, P>(&self, bucket: B, object: O, payload: T) -> WriteObject<P, S>
190 where
191 B: Into<String>,
192 O: Into<String>,
193 T: Into<Payload<P>>,
194 {
195 WriteObject::new(
196 self.stub.clone(),
197 bucket,
198 object,
199 payload,
200 self.options.clone(),
201 )
202 }
203
204 /// Reads the contents of an object.
205 ///
206 /// # Example
207 /// ```
208 /// # use google_cloud_storage::client::Storage;
209 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
210 /// let mut resp = client
211 /// .read_object("projects/_/buckets/my-bucket", "my-object")
212 /// .send()
213 /// .await?;
214 /// let mut contents = Vec::new();
215 /// while let Some(chunk) = resp.next().await.transpose()? {
216 /// contents.extend_from_slice(&chunk);
217 /// }
218 /// println!("object contents={:?}", bytes::Bytes::from_owner(contents));
219 /// # Ok(()) }
220 /// ```
221 ///
222 /// # Parameters
223 /// * `bucket` - the bucket name containing the object. In
224 /// `projects/_/buckets/{bucket_id}` format.
225 /// * `object` - the object name.
226 pub fn read_object<B, O>(&self, bucket: B, object: O) -> ReadObject<S>
227 where
228 B: Into<String>,
229 O: Into<String>,
230 {
231 ReadObject::new(self.stub.clone(), bucket, object, self.options.clone())
232 }
233
234 #[cfg(google_cloud_unstable_storage_bidi)]
235 /// Opens an object to read its contents using concurrent ranged reads.
236 ///
237 /// # Example
238 /// ```
239 /// # use google_cloud_storage::client::Storage;
240 /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
241 /// use google_cloud_storage::model_ext::ReadRange;
242 /// let descriptor = client
243 /// .open_object("projects/_/buckets/my-bucket", "my-object")
244 /// .send()
245 /// .await?;
246 /// // Print the object metadata
247 /// println!("metadata = {:?}", descriptor.object());
248 /// // Read 2000 bytes starting at offset 1000.
249 /// let mut reader = descriptor.read_range(ReadRange::segment(1000, 2000)).await;
250 /// let mut contents = Vec::new();
251 /// while let Some(chunk) = reader.next().await.transpose()? {
252 /// contents.extend_from_slice(&chunk);
253 /// }
254 /// println!("range contents={:?}", bytes::Bytes::from_owner(contents));
255 /// // `descriptor` can be used to read more ranges, concurrently if needed.
256 /// # Ok(()) }
257 /// ```
258 ///
259 /// # Parameters
260 /// * `bucket` - the bucket name containing the object. In
261 /// `projects/_/buckets/{bucket_id}` format.
262 /// * `object` - the object name.
263 pub fn open_object<B, O>(&self, bucket: B, object: O) -> OpenObject<S>
264 where
265 B: Into<String>,
266 O: Into<String>,
267 {
268 OpenObject::new(
269 bucket.into(),
270 object.into(),
271 self.stub.clone(),
272 self.options.clone(),
273 )
274 }
275}
276
277impl Storage {
278 pub(crate) async fn new(builder: ClientBuilder) -> BuilderResult<Self> {
279 let client = reqwest::Client::builder()
280 // Disable all automatic decompression. These could be enabled by users by enabling
281 // the corresponding features flags, but we will not be able to tell whether this
282 // has happened.
283 .no_brotli()
284 .no_deflate()
285 .no_gzip()
286 .no_zstd()
287 .build()
288 .map_err(BuilderError::transport)?;
289 let inner = StorageInner::from_parts(client, builder).await?;
290 let options = inner.options.clone();
291 let stub = crate::storage::transport::Storage::new(Arc::new(inner));
292 Ok(Self { stub, options })
293 }
294}
295
296impl StorageInner {
297 /// Builds a client assuming `config.cred` and `config.endpoint` are initialized, panics otherwise.
298 pub(self) fn new(
299 client: reqwest::Client,
300 cred: Credentials,
301 endpoint: String,
302 options: RequestOptions,
303 #[cfg(google_cloud_unstable_storage_bidi)] grpc: gaxi::grpc::Client,
304 ) -> Self {
305 Self {
306 client,
307 cred,
308 endpoint,
309 options,
310 #[cfg(google_cloud_unstable_storage_bidi)]
311 grpc,
312 }
313 }
314
315 pub(self) async fn from_parts(
316 client: reqwest::Client,
317 builder: ClientBuilder,
318 ) -> BuilderResult<Self> {
319 let (config, options) = builder.into_parts()?;
320 let endpoint = config
321 .endpoint
322 .clone()
323 .expect("into_parts() assigns a default endpoint");
324 let cred = config
325 .cred
326 .clone()
327 .expect("into_parts() assigns default credentials");
328
329 let inner = StorageInner::new(
330 client,
331 cred,
332 endpoint,
333 options,
334 #[cfg(google_cloud_unstable_storage_bidi)]
335 gaxi::grpc::Client::new(config, super::DEFAULT_HOST).await?,
336 );
337 Ok(inner)
338 }
339
340 // Helper method to apply authentication headers to the request builder.
341 pub async fn apply_auth_headers(
342 &self,
343 builder: reqwest::RequestBuilder,
344 ) -> crate::Result<reqwest::RequestBuilder> {
345 let cached_auth_headers = self
346 .cred
347 .headers(Extensions::new())
348 .await
349 .map_err(Error::authentication)?;
350
351 let auth_headers = match cached_auth_headers {
352 CacheableResource::New { data, .. } => data,
353 CacheableResource::NotModified => {
354 unreachable!("headers are not cached");
355 }
356 };
357
358 let builder = builder.headers(auth_headers);
359 Ok(builder)
360 }
361}
362
363/// A builder for [Storage].
364///
365/// ```
366/// # use google_cloud_storage::client::Storage;
367/// # async fn sample() -> anyhow::Result<()> {
368/// let builder = Storage::builder();
369/// let client = builder
370/// .with_endpoint("https://storage.googleapis.com")
371/// .build()
372/// .await?;
373/// # Ok(()) }
374/// ```
375pub struct ClientBuilder {
376 // Common options for all clients (generated or not).
377 pub(crate) config: ClientConfig,
378 // Specific options for the storage client. `RequestOptions` also requires
379 // these, it makes sense to share them.
380 common_options: CommonOptions,
381}
382
383impl ClientBuilder {
384 pub(crate) fn new() -> Self {
385 let mut config = ClientConfig::default();
386 config.retry_policy = Some(Arc::new(crate::retry_policy::storage_default()));
387 config.backoff_policy = Some(Arc::new(crate::backoff_policy::default()));
388 let common_options = CommonOptions::new();
389 Self {
390 config,
391 common_options,
392 }
393 }
394
395 /// Creates a new client.
396 ///
397 /// # Example
398 /// ```
399 /// # use google_cloud_storage::client::Storage;
400 /// # async fn sample() -> anyhow::Result<()> {
401 /// let client = Storage::builder().build().await?;
402 /// # Ok(()) }
403 /// ```
404 pub async fn build(self) -> BuilderResult<Storage> {
405 Storage::new(self).await
406 }
407
408 /// Sets the endpoint.
409 ///
410 /// # Example
411 /// ```
412 /// # use google_cloud_storage::client::Storage;
413 /// # async fn sample() -> anyhow::Result<()> {
414 /// let client = Storage::builder()
415 /// .with_endpoint("https://private.googleapis.com")
416 /// .build()
417 /// .await?;
418 /// # Ok(()) }
419 /// ```
420 pub fn with_endpoint<V: Into<String>>(mut self, v: V) -> Self {
421 self.config.endpoint = Some(v.into());
422 self
423 }
424
425 /// Configures the authentication credentials.
426 ///
427 /// Google Cloud Storage requires authentication for most buckets. Use this
428 /// method to change the credentials used by the client. More information
429 /// about valid credentials types can be found in the [google-cloud-auth]
430 /// crate documentation.
431 ///
432 /// # Example
433 /// ```
434 /// # use google_cloud_storage::client::Storage;
435 /// # async fn sample() -> anyhow::Result<()> {
436 /// use auth::credentials::mds;
437 /// let client = Storage::builder()
438 /// .with_credentials(
439 /// mds::Builder::default()
440 /// .with_scopes(["https://www.googleapis.com/auth/cloud-platform.read-only"])
441 /// .build()?)
442 /// .build()
443 /// .await?;
444 /// # Ok(()) }
445 /// ```
446 ///
447 /// [google-cloud-auth]: https://docs.rs/google-cloud-auth
448 pub fn with_credentials<V: Into<auth::credentials::Credentials>>(mut self, v: V) -> Self {
449 self.config.cred = Some(v.into());
450 self
451 }
452
453 /// Configure the retry policy.
454 ///
455 /// The client libraries can automatically retry operations that fail. The
456 /// retry policy controls what errors are considered retryable, sets limits
457 /// on the number of attempts or the time trying to make attempts.
458 ///
459 /// # Example
460 /// ```
461 /// # use google_cloud_storage::client::Storage;
462 /// # async fn sample() -> anyhow::Result<()> {
463 /// use gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
464 /// let client = Storage::builder()
465 /// .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
466 /// .build()
467 /// .await?;
468 /// # Ok(()) }
469 /// ```
470 pub fn with_retry_policy<V: Into<gax::retry_policy::RetryPolicyArg>>(mut self, v: V) -> Self {
471 self.config.retry_policy = Some(v.into().into());
472 self
473 }
474
475 /// Configure the retry backoff policy.
476 ///
477 /// The client libraries can automatically retry operations that fail. The
478 /// backoff policy controls how long to wait in between retry attempts.
479 ///
480 /// # Example
481 /// ```
482 /// # use google_cloud_storage::client::Storage;
483 /// # async fn sample() -> anyhow::Result<()> {
484 /// use gax::exponential_backoff::ExponentialBackoff;
485 /// use std::time::Duration;
486 /// let policy = ExponentialBackoff::default();
487 /// let client = Storage::builder()
488 /// .with_backoff_policy(policy)
489 /// .build()
490 /// .await?;
491 /// # Ok(()) }
492 /// ```
493 pub fn with_backoff_policy<V: Into<gax::backoff_policy::BackoffPolicyArg>>(
494 mut self,
495 v: V,
496 ) -> Self {
497 self.config.backoff_policy = Some(v.into().into());
498 self
499 }
500
501 /// Configure the retry throttler.
502 ///
503 /// Advanced applications may want to configure a retry throttler to
504 /// [Address Cascading Failures] and when [Handling Overload] conditions.
505 /// The client libraries throttle their retry loop, using a policy to
506 /// control the throttling algorithm. Use this method to fine tune or
507 /// customize the default retry throtler.
508 ///
509 /// [Handling Overload]: https://sre.google/sre-book/handling-overload/
510 /// [Addressing Cascading Failures]: https://sre.google/sre-book/addressing-cascading-failures/
511 ///
512 /// # Example
513 /// ```
514 /// # use google_cloud_storage::client::Storage;
515 /// # async fn sample() -> anyhow::Result<()> {
516 /// use gax::retry_throttler::AdaptiveThrottler;
517 /// let client = Storage::builder()
518 /// .with_retry_throttler(AdaptiveThrottler::default())
519 /// .build()
520 /// .await?;
521 /// # Ok(()) }
522 /// ```
523 pub fn with_retry_throttler<V: Into<gax::retry_throttler::RetryThrottlerArg>>(
524 mut self,
525 v: V,
526 ) -> Self {
527 self.config.retry_throttler = v.into().into();
528 self
529 }
530
531 /// Sets the payload size threshold to switch from single-shot to resumable uploads.
532 ///
533 /// # Example
534 /// ```
535 /// # use google_cloud_storage::client::Storage;
536 /// # async fn sample() -> anyhow::Result<()> {
537 /// let client = Storage::builder()
538 /// .with_resumable_upload_threshold(0_usize) // Forces a resumable upload.
539 /// .build()
540 /// .await?;
541 /// let response = client
542 /// .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
543 /// .send_buffered()
544 /// .await?;
545 /// println!("response details={response:?}");
546 /// # Ok(()) }
547 /// ```
548 ///
549 /// The client library can write objects using [single-shot] or [resumable]
550 /// uploads. For small objects, single-shot uploads offer better
551 /// performance, as they require a single HTTP transfer. For larger objects,
552 /// the additional request latency is not significant, and resumable uploads
553 /// offer better recovery on errors.
554 ///
555 /// The library automatically selects resumable uploads when the payload is
556 /// equal to or larger than this option. For smaller writes the client
557 /// library uses single-shot uploads.
558 ///
559 /// The exact threshold depends on where the application is deployed and
560 /// destination bucket location with respect to where the application is
561 /// running. The library defaults should work well in most cases, but some
562 /// applications may benefit from fine-tuning.
563 ///
564 /// [single-shot]: https://cloud.google.com/storage/docs/uploading-objects
565 /// [resumable]: https://cloud.google.com/storage/docs/resumable-uploads
566 pub fn with_resumable_upload_threshold<V: Into<usize>>(mut self, v: V) -> Self {
567 self.common_options.resumable_upload_threshold = v.into();
568 self
569 }
570
571 /// Changes the buffer size for some resumable uploads.
572 ///
573 /// # Example
574 /// ```
575 /// # use google_cloud_storage::client::Storage;
576 /// # async fn sample() -> anyhow::Result<()> {
577 /// let client = Storage::builder()
578 /// .with_resumable_upload_buffer_size(32 * 1024 * 1024_usize)
579 /// .build()
580 /// .await?;
581 /// let response = client
582 /// .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
583 /// .send_buffered()
584 /// .await?;
585 /// println!("response details={response:?}");
586 /// # Ok(()) }
587 /// ```
588 ///
589 /// When performing [resumable uploads] from sources without [Seek] the
590 /// client library needs to buffer data in memory until it is persisted by
591 /// the service. Otherwise the data would be lost if the upload is
592 /// interrupted. Applications may want to tune this buffer size:
593 ///
594 /// - Use smaller buffer sizes to support more concurrent writes in the
595 /// same application.
596 /// - Use larger buffer sizes for better throughput. Sending many small
597 /// buffers stalls the writer until the client receives a successful
598 /// response from the service.
599 ///
600 /// Keep in mind that there are diminishing returns on using larger buffers.
601 ///
602 /// [resumable uploads]: https://cloud.google.com/storage/docs/resumable-uploads
603 /// [Seek]: crate::streaming_source::Seek
604 pub fn with_resumable_upload_buffer_size<V: Into<usize>>(mut self, v: V) -> Self {
605 self.common_options.resumable_upload_buffer_size = v.into();
606 self
607 }
608
609 /// Configure the resume policy for object reads.
610 ///
611 /// The Cloud Storage client library can automatically resume a read request
612 /// that is interrupted by a transient error. Applications may want to
613 /// limit the number of read attempts, or may wish to expand the type
614 /// of errors treated as retryable.
615 ///
616 /// # Example
617 /// ```
618 /// # use google_cloud_storage::client::Storage;
619 /// # async fn sample() -> anyhow::Result<()> {
620 /// use google_cloud_storage::read_resume_policy::{AlwaysResume, ReadResumePolicyExt};
621 /// let client = Storage::builder()
622 /// .with_read_resume_policy(AlwaysResume.with_attempt_limit(3))
623 /// .build()
624 /// .await?;
625 /// # Ok(()) }
626 /// ```
627 pub fn with_read_resume_policy<V>(mut self, v: V) -> Self
628 where
629 V: ReadResumePolicy + 'static,
630 {
631 self.common_options.read_resume_policy = Arc::new(v);
632 self
633 }
634
635 pub(crate) fn apply_default_credentials(&mut self) -> BuilderResult<()> {
636 if self.config.cred.is_some() {
637 return Ok(());
638 };
639 let default = auth::credentials::Builder::default()
640 .build()
641 .map_err(BuilderError::cred)?;
642 self.config.cred = Some(default);
643 Ok(())
644 }
645
646 pub(crate) fn apply_default_endpoint(&mut self) -> BuilderResult<()> {
647 let _ = self
648 .config
649 .endpoint
650 .get_or_insert_with(|| super::DEFAULT_HOST.to_string());
651 Ok(())
652 }
653
654 // Breaks the builder into its parts, with defaults applied.
655 pub(crate) fn into_parts(
656 mut self,
657 ) -> gax::client_builder::Result<(ClientConfig, RequestOptions)> {
658 self.apply_default_credentials()?;
659 self.apply_default_endpoint()?;
660 let request_options =
661 RequestOptions::new_with_client_config(&self.config, self.common_options);
662 Ok((self.config, request_options))
663 }
664}
665
666/// The set of characters that are percent encoded.
667///
668/// This set is defined at https://cloud.google.com/storage/docs/request-endpoints#encoding:
669///
670/// Encode the following characters when they appear in either the object name
671/// or query string of a request URL:
672/// !, #, $, &, ', (, ), *, +, ,, /, :, ;, =, ?, @, [, ], and space characters.
673const ENCODED_CHARS: percent_encoding::AsciiSet = percent_encoding::CONTROLS
674 .add(b'!')
675 .add(b'#')
676 .add(b'$')
677 .add(b'&')
678 .add(b'\'')
679 .add(b'(')
680 .add(b')')
681 .add(b'*')
682 .add(b'+')
683 .add(b',')
684 .add(b'/')
685 .add(b':')
686 .add(b';')
687 .add(b'=')
688 .add(b'?')
689 .add(b'@')
690 .add(b'[')
691 .add(b']')
692 .add(b' ');
693
694/// Percent encode a string.
695///
696/// To ensure compatibility certain characters need to be encoded when they appear
697/// in either the object name or query string of a request URL.
698pub(crate) fn enc(value: &str) -> String {
699 percent_encoding::utf8_percent_encode(value, &ENCODED_CHARS).to_string()
700}
701
702pub(crate) fn apply_customer_supplied_encryption_headers(
703 builder: reqwest::RequestBuilder,
704 common_object_request_params: &Option<crate::model::CommonObjectRequestParams>,
705) -> reqwest::RequestBuilder {
706 common_object_request_params.iter().fold(builder, |b, v| {
707 b.header(
708 "x-goog-encryption-algorithm",
709 v.encryption_algorithm.clone(),
710 )
711 .header(
712 "x-goog-encryption-key",
713 BASE64_STANDARD.encode(v.encryption_key_bytes.clone()),
714 )
715 .header(
716 "x-goog-encryption-key-sha256",
717 BASE64_STANDARD.encode(v.encryption_key_sha256_bytes.clone()),
718 )
719 })
720}
721
722#[cfg(test)]
723pub(crate) mod tests {
724 use super::*;
725 use gax::retry_result::RetryResult;
726 use gax::retry_state::RetryState;
727 use std::{sync::Arc, time::Duration};
728
729 pub(crate) fn test_builder() -> ClientBuilder {
730 ClientBuilder::new()
731 .with_credentials(auth::credentials::anonymous::Builder::new().build())
732 .with_endpoint("http://private.googleapis.com")
733 .with_backoff_policy(
734 gax::exponential_backoff::ExponentialBackoffBuilder::new()
735 .with_initial_delay(Duration::from_millis(1))
736 .with_maximum_delay(Duration::from_millis(2))
737 .build()
738 .expect("hard coded policy should build correctly"),
739 )
740 }
741
742 /// This is used by the request builder tests.
743 pub(crate) async fn test_inner_client(builder: ClientBuilder) -> Arc<StorageInner> {
744 let client = reqwest::Client::new();
745 let inner = StorageInner::from_parts(client, builder)
746 .await
747 .expect("creating an test inner client succeeds");
748 Arc::new(inner)
749 }
750
751 mockall::mock! {
752 #[derive(Debug)]
753 pub RetryThrottler {}
754
755 impl gax::retry_throttler::RetryThrottler for RetryThrottler {
756 fn throttle_retry_attempt(&self) -> bool;
757 fn on_retry_failure(&mut self, flow: &RetryResult);
758 fn on_success(&mut self);
759 }
760 }
761
762 mockall::mock! {
763 #[derive(Debug)]
764 pub RetryPolicy {}
765
766 impl gax::retry_policy::RetryPolicy for RetryPolicy {
767 fn on_error(&self, state: &RetryState, error: gax::error::Error) -> RetryResult;
768 }
769 }
770
771 mockall::mock! {
772 #[derive(Debug)]
773 pub BackoffPolicy {}
774
775 impl gax::backoff_policy::BackoffPolicy for BackoffPolicy {
776 fn on_failure(&self, state: &RetryState) -> std::time::Duration;
777 }
778 }
779
780 mockall::mock! {
781 #[derive(Debug)]
782 pub ReadResumePolicy {}
783
784 impl crate::read_resume_policy::ReadResumePolicy for ReadResumePolicy {
785 fn on_error(&self, query: &crate::read_resume_policy::ResumeQuery, error: gax::error::Error) -> crate::read_resume_policy::ResumeResult;
786 }
787 }
788}