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