Skip to main content

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