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