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