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