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}