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}