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::UploadObject;
19use crate::download_resume_policy::DownloadResumePolicy;
20use crate::error::KeyAes256Error;
21use crate::storage::checksum::details::Crc32c;
22use crate::upload_source::Payload;
23use auth::credentials::CacheableResource;
24use base64::Engine;
25use base64::prelude::BASE64_STANDARD;
26use http::Extensions;
27use sha2::{Digest, Sha256};
28use std::sync::Arc;
29
30/// Implements a client for the Cloud Storage API.
31///
32/// # Example
33/// ```
34/// # tokio_test::block_on(async {
35/// # use google_cloud_storage::client::Storage;
36/// let client = Storage::builder().build().await?;
37/// // use `client` to make requests to Cloud Storage.
38/// # gax::client_builder::Result::<()>::Ok(()) });
39/// ```
40///
41/// # Configuration
42///
43/// To configure `Storage` use the `with_*` methods in the type returned
44/// by [builder()][Storage::builder]. The default configuration should
45/// work for most applications. Common configuration changes include
46///
47/// * [with_endpoint()]: by default this client uses the global default endpoint
48///   (`https://storage.googleapis.com`). Applications using regional
49///   endpoints or running in restricted networks (e.g. a network configured
50///   with [Private Google Access with VPC Service Controls]) may want to
51///   override this default.
52/// * [with_credentials()]: by default this client uses
53///   [Application Default Credentials]. Applications using custom
54///   authentication may need to override this default.
55///
56/// # Pooling and Cloning
57///
58/// `Storage` holds a connection pool internally, it is advised to
59/// create one and then reuse it.  You do not need to wrap `Storage` in
60/// an [Rc](std::rc::Rc) or [Arc] to reuse it, because it already uses an `Arc`
61/// internally.
62///
63/// # Service Description
64///
65/// The Cloud Storage API allows applications to read and write data through
66/// the abstractions of buckets and objects. For a description of these
67/// abstractions please see <https://cloud.google.com/storage/docs>.
68///
69/// Resources are named as follows:
70///
71/// - Projects are referred to as they are defined by the Resource Manager API,
72///   using strings like `projects/123456` or `projects/my-string-id`.
73///
74/// - Buckets are named using string names of the form:
75///   `projects/{project}/buckets/{bucket}`
76///   For globally unique buckets, `_` may be substituted for the project.
77///
78/// - Objects are uniquely identified by their name along with the name of the
79///   bucket they belong to, as separate strings in this API. For example:
80///   ```no_rust
81///   bucket = "projects/_/buckets/my-bucket"
82///   object = "my-object/with/a/folder-like/name"
83///   ```
84///   Note that object names can contain `/` characters, which are treated as
85///   any other character (no special directory semantics).
86///
87/// [with_endpoint()]: ClientBuilder::with_endpoint
88/// [with_credentials()]: ClientBuilder::with_credentials
89/// [Private Google Access with VPC Service Controls]: https://cloud.google.com/vpc-service-controls/docs/private-connectivity
90/// [Application Default Credentials]: https://cloud.google.com/docs/authentication#adc
91#[derive(Clone, Debug)]
92pub struct Storage {
93    inner: std::sync::Arc<StorageInner>,
94}
95
96#[derive(Clone, Debug)]
97pub(crate) struct StorageInner {
98    pub client: reqwest::Client,
99    pub cred: auth::credentials::Credentials,
100    pub endpoint: String,
101    pub options: RequestOptions,
102}
103
104impl Storage {
105    /// Returns a builder for [Storage].
106    ///
107    /// # Example
108    /// ```
109    /// # use google_cloud_storage::client::Storage;
110    /// # async fn sample() -> anyhow::Result<()> {
111    /// let client = Storage::builder().build().await?;
112    /// # Ok(()) }
113    /// ```
114    pub fn builder() -> ClientBuilder {
115        ClientBuilder::new()
116    }
117
118    /// Upload an object using a local buffer.
119    ///
120    /// If the data source does **not** implement [Seek] the client library must
121    /// buffer uploaded data until this data is persisted in the service. This
122    /// requires more memory in the client, and when the buffer grows too large,
123    /// may require stalling the upload until the service can persist the data.
124    ///
125    /// Use this function for data sources representing computations where
126    /// it is expensive or impossible to restart said computation. This function
127    /// is also useful when it is hard or impossible to predict the number of
128    /// bytes emitted by a stream, even if restarting the stream is not too
129    /// expensive.
130    ///
131    /// # Example
132    /// ```
133    /// # use google_cloud_storage::client::Storage;
134    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
135    /// let response = client
136    ///     .upload_object("projects/_/buckets/my-bucket", "my-object", "hello world")
137    ///     .send_buffered()
138    ///     .await?;
139    /// println!("response details={response:?}");
140    /// # Ok(()) }
141    /// ```
142    ///
143    /// # Example
144    /// ```
145    /// # use google_cloud_storage::client::Storage;
146    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
147    /// let response = client
148    ///     .upload_object("projects/_/buckets/my-bucket", "my-object", "hello world")
149    ///     .send_unbuffered()
150    ///     .await?;
151    /// println!("response details={response:?}");
152    /// # Ok(()) }
153    /// ```
154    ///
155    /// # Parameters
156    /// * `bucket` - the bucket name containing the object. In
157    ///   `projects/_/buckets/{bucket_id}` format.
158    /// * `object` - the object name.
159    /// * `payload` - the object data.
160    ///
161    /// [Seek]: crate::upload_source::Seek
162    pub fn upload_object<B, O, T, P>(
163        &self,
164        bucket: B,
165        object: O,
166        payload: T,
167    ) -> UploadObject<P, Crc32c>
168    where
169        B: Into<String>,
170        O: Into<String>,
171        T: Into<Payload<P>>,
172    {
173        UploadObject::new(self.inner.clone(), bucket, object, payload)
174    }
175
176    /// Downloads the contents of an object.
177    ///
178    /// # Example
179    /// ```
180    /// # use google_cloud_storage::client::Storage;
181    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
182    /// let mut resp = client
183    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
184    ///     .send()
185    ///     .await?;
186    /// let mut contents = Vec::new();
187    /// while let Some(chunk) = resp.next().await.transpose()? {
188    ///   contents.extend_from_slice(&chunk);
189    /// }
190    /// println!("object contents={:?}", bytes::Bytes::from_owner(contents));
191    /// # Ok(()) }
192    /// ```
193    ///
194    /// # Parameters
195    /// * `bucket` - the bucket name containing the object. In
196    ///   `projects/_/buckets/{bucket_id}` format.
197    /// * `object` - the object name.
198    pub fn read_object<B, O>(&self, bucket: B, object: O) -> ReadObject
199    where
200        B: Into<String>,
201        O: Into<String>,
202    {
203        ReadObject::new(self.inner.clone(), bucket, object)
204    }
205
206    pub(crate) fn new(builder: ClientBuilder) -> gax::client_builder::Result<Self> {
207        use gax::client_builder::Error;
208        let client = reqwest::Client::builder()
209            // Disable all automatic decompression. These could be enabled by users by enabling
210            // the corresponding features flags, but we will not be able to tell whether this
211            // has happened.
212            .no_brotli()
213            .no_deflate()
214            .no_gzip()
215            .no_zstd()
216            .build()
217            .map_err(Error::transport)?;
218        let mut builder = builder;
219        let cred = if let Some(c) = builder.credentials {
220            c
221        } else {
222            auth::credentials::Builder::default()
223                .build()
224                .map_err(Error::cred)?
225        };
226        let endpoint = builder
227            .endpoint
228            .unwrap_or_else(|| self::DEFAULT_HOST.to_string());
229        builder.credentials = Some(cred);
230        builder.endpoint = Some(endpoint);
231        let inner = Arc::new(StorageInner::new(client, builder));
232        Ok(Self { inner })
233    }
234}
235
236impl StorageInner {
237    /// Builds a client assuming `config.cred` and `config.endpoint` are initialized, panics otherwise.
238    pub(self) fn new(client: reqwest::Client, builder: ClientBuilder) -> Self {
239        Self {
240            client,
241            cred: builder
242                .credentials
243                .expect("StorageInner assumes the credentials are initialized"),
244            endpoint: builder
245                .endpoint
246                .expect("StorageInner assumes the endpoint is initialized"),
247            options: builder.default_options,
248        }
249    }
250
251    // Helper method to apply authentication headers to the request builder.
252    pub async fn apply_auth_headers(
253        &self,
254        builder: reqwest::RequestBuilder,
255    ) -> crate::Result<reqwest::RequestBuilder> {
256        let cached_auth_headers = self
257            .cred
258            .headers(Extensions::new())
259            .await
260            .map_err(Error::authentication)?;
261
262        let auth_headers = match cached_auth_headers {
263            CacheableResource::New { data, .. } => data,
264            CacheableResource::NotModified => {
265                unreachable!("headers are not cached");
266            }
267        };
268
269        let builder = builder.headers(auth_headers);
270        Ok(builder)
271    }
272}
273
274/// A builder for [Storage].
275///
276/// ```
277/// # use google_cloud_storage::client::Storage;
278/// # async fn sample() -> anyhow::Result<()> {
279/// let builder = Storage::builder();
280/// let client = builder
281///     .with_endpoint("https://storage.googleapis.com")
282///     .build()
283///     .await?;
284/// # Ok(()) }
285/// ```
286pub struct ClientBuilder {
287    pub(crate) endpoint: Option<String>,
288    pub(crate) credentials: Option<auth::credentials::Credentials>,
289    // Default options for requests.
290    pub(crate) default_options: RequestOptions,
291}
292
293impl ClientBuilder {
294    pub(crate) fn new() -> Self {
295        Self {
296            endpoint: None,
297            credentials: None,
298            default_options: RequestOptions::new(),
299        }
300    }
301
302    /// Creates a new client.
303    ///
304    /// # Example
305    /// ```
306    /// # use google_cloud_storage::client::Storage;
307    /// # async fn sample() -> anyhow::Result<()> {
308    /// let client = Storage::builder().build().await?;
309    /// # Ok(()) }
310    /// ```
311    pub async fn build(self) -> gax::client_builder::Result<Storage> {
312        Storage::new(self)
313    }
314
315    /// Sets the endpoint.
316    ///
317    /// # Example
318    /// ```
319    /// # use google_cloud_storage::client::Storage;
320    /// # async fn sample() -> anyhow::Result<()> {
321    /// let client = Storage::builder()
322    ///     .with_endpoint("https://private.googleapis.com")
323    ///     .build()
324    ///     .await?;
325    /// # Ok(()) }
326    /// ```
327    pub fn with_endpoint<V: Into<String>>(mut self, v: V) -> Self {
328        self.endpoint = Some(v.into());
329        self
330    }
331
332    /// Configures the authentication credentials.
333    ///
334    /// Google Cloud Storage requires authentication for most buckets. Use this
335    /// method to change the credentials used by the client. More information
336    /// about valid credentials types can be found in the [google-cloud-auth]
337    /// crate documentation.
338    ///
339    /// # Example
340    /// ```
341    /// # use google_cloud_storage::client::Storage;
342    /// # async fn sample() -> anyhow::Result<()> {
343    /// use auth::credentials::mds;
344    /// let client = Storage::builder()
345    ///     .with_credentials(
346    ///         mds::Builder::default()
347    ///             .with_scopes(["https://www.googleapis.com/auth/cloud-platform.read-only"])
348    ///             .build()?)
349    ///     .build()
350    ///     .await?;
351    /// # Ok(()) }
352    /// ```
353    ///
354    /// [google-cloud-auth]: https://docs.rs/google-cloud-auth
355    pub fn with_credentials<V: Into<auth::credentials::Credentials>>(mut self, v: V) -> Self {
356        self.credentials = Some(v.into());
357        self
358    }
359
360    /// Configure the retry policy.
361    ///
362    /// The client libraries can automatically retry operations that fail. The
363    /// retry policy controls what errors are considered retryable, sets limits
364    /// on the number of attempts or the time trying to make attempts.
365    ///
366    /// # Example
367    /// ```
368    /// # use google_cloud_storage::client::Storage;
369    /// # async fn sample() -> anyhow::Result<()> {
370    /// use gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
371    /// let client = Storage::builder()
372    ///     .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
373    ///     .build()
374    ///     .await?;
375    /// # Ok(()) }
376    /// ```
377    pub fn with_retry_policy<V: Into<gax::retry_policy::RetryPolicyArg>>(mut self, v: V) -> Self {
378        self.default_options.retry_policy = v.into().into();
379        self
380    }
381
382    /// Configure the retry backoff policy.
383    ///
384    /// The client libraries can automatically retry operations that fail. The
385    /// backoff policy controls how long to wait in between retry attempts.
386    ///
387    /// # Example
388    /// ```
389    /// # use google_cloud_storage::client::Storage;
390    /// # async fn sample() -> anyhow::Result<()> {
391    /// use gax::exponential_backoff::ExponentialBackoff;
392    /// use std::time::Duration;
393    /// let policy = ExponentialBackoff::default();
394    /// let client = Storage::builder()
395    ///     .with_backoff_policy(policy)
396    ///     .build()
397    ///     .await?;
398    /// # Ok(()) }
399    /// ```
400    pub fn with_backoff_policy<V: Into<gax::backoff_policy::BackoffPolicyArg>>(
401        mut self,
402        v: V,
403    ) -> Self {
404        self.default_options.backoff_policy = v.into().into();
405        self
406    }
407
408    /// Configure the retry throttler.
409    ///
410    /// Advanced applications may want to configure a retry throttler to
411    /// [Address Cascading Failures] and when [Handling Overload] conditions.
412    /// The client libraries throttle their retry loop, using a policy to
413    /// control the throttling algorithm. Use this method to fine tune or
414    /// customize the default retry throtler.
415    ///
416    /// [Handling Overload]: https://sre.google/sre-book/handling-overload/
417    /// [Addressing Cascading Failures]: https://sre.google/sre-book/addressing-cascading-failures/
418    ///
419    /// # Example
420    /// ```
421    /// # use google_cloud_storage::client::Storage;
422    /// # async fn sample() -> anyhow::Result<()> {
423    /// use gax::retry_throttler::AdaptiveThrottler;
424    /// let client = Storage::builder()
425    ///     .with_retry_throttler(AdaptiveThrottler::default())
426    ///     .build()
427    ///     .await?;
428    /// # Ok(()) }
429    /// ```
430    pub fn with_retry_throttler<V: Into<gax::retry_throttler::RetryThrottlerArg>>(
431        mut self,
432        v: V,
433    ) -> Self {
434        self.default_options.retry_throttler = v.into().into();
435        self
436    }
437
438    /// Sets the payload size threshold to switch from single-shot to resumable uploads.
439    ///
440    /// # Example
441    /// ```
442    /// # use google_cloud_storage::client::Storage;
443    /// # async fn sample() -> anyhow::Result<()> {
444    /// let client = Storage::builder()
445    ///     .with_resumable_upload_threshold(0_usize) // Forces a resumable upload.
446    ///     .build()
447    ///     .await?;
448    /// let response = client
449    ///     .upload_object("projects/_/buckets/my-bucket", "my-object", "hello world")
450    ///     .send_buffered()
451    ///     .await?;
452    /// println!("response details={response:?}");
453    /// # Ok(()) }
454    /// ```
455    ///
456    /// The client library can perform uploads using [single-shot] or
457    /// [resumable] uploads. For small objects, single-shot uploads offer better
458    /// performance, as they require a single HTTP transfer. For larger objects,
459    /// the additional request latency is not significant, and resumable uploads
460    /// offer better recovery on errors.
461    ///
462    /// The library automatically selects resumable uploads when the payload is
463    /// equal to or larger than this option. For smaller uploads the client
464    /// library uses single-shot uploads.
465    ///
466    /// The exact threshold depends on where the application is deployed and
467    /// destination bucket location with respect to where the application is
468    /// running. The library defaults should work well in most cases, but some
469    /// applications may benefit from fine-tuning.
470    ///
471    /// [single-shot]: https://cloud.google.com/storage/docs/uploading-objects
472    /// [resumable]: https://cloud.google.com/storage/docs/resumable-uploads
473    pub fn with_resumable_upload_threshold<V: Into<usize>>(mut self, v: V) -> Self {
474        self.default_options.resumable_upload_threshold = v.into();
475        self
476    }
477
478    /// Changes the buffer size for some resumable uploads.
479    ///
480    /// # Example
481    /// ```
482    /// # use google_cloud_storage::client::Storage;
483    /// # async fn sample() -> anyhow::Result<()> {
484    /// let client = Storage::builder()
485    ///     .with_resumable_upload_buffer_size(32 * 1024 * 1024_usize)
486    ///     .build()
487    ///     .await?;
488    /// let response = client
489    ///     .upload_object("projects/_/buckets/my-bucket", "my-object", "hello world")
490    ///     .send_buffered()
491    ///     .await?;
492    /// println!("response details={response:?}");
493    /// # Ok(()) }
494    /// ```
495    ///
496    /// When performing [resumable uploads] from sources without [Seek] the
497    /// client library needs to buffer data in memory until it is persisted by
498    /// the service. Otherwise the data would be lost if the upload fails.
499    /// Applications may want to tune this buffer size:
500    ///
501    /// - Use smaller buffer sizes to support more concurrent uploads in the
502    ///   same application.
503    /// - Use larger buffer sizes for better throughput. Sending many small
504    ///   buffers stalls the upload until the client receives a successful
505    ///   response from the service.
506    ///
507    /// Keep in mind that there are diminishing returns on using larger buffers.
508    ///
509    /// [resumable uploads]: https://cloud.google.com/storage/docs/resumable-uploads
510    /// [Seek]: crate::upload_source::Seek
511    pub fn with_resumable_upload_buffer_size<V: Into<usize>>(mut self, v: V) -> Self {
512        self.default_options.resumable_upload_buffer_size = v.into();
513        self
514    }
515
516    /// Configure the resume policy for downloads.
517    ///
518    /// The Cloud Storage client library can automatically resume a download
519    /// that is interrupted by a transient error. Applications may want to
520    /// limit the number of download attempts, or may wish to expand the type
521    /// of errors treated as retryable.
522    ///
523    /// # Example
524    /// ```
525    /// # use google_cloud_storage::client::Storage;
526    /// # async fn sample() -> anyhow::Result<()> {
527    /// use google_cloud_storage::download_resume_policy::{AlwaysResume, DownloadResumePolicyExt};
528    /// let client = Storage::builder()
529    ///     .with_download_resume_policy(AlwaysResume.with_attempt_limit(3))
530    ///     .build()
531    ///     .await?;
532    /// # Ok(()) }
533    /// ```
534    pub fn with_download_resume_policy<V>(mut self, v: V) -> Self
535    where
536        V: DownloadResumePolicy + 'static,
537    {
538        self.default_options.download_resume_policy = Arc::new(v);
539        self
540    }
541}
542
543/// The default host used by the service.
544const DEFAULT_HOST: &str = "https://storage.googleapis.com";
545
546pub(crate) mod info {
547    const NAME: &str = env!("CARGO_PKG_NAME");
548    const VERSION: &str = env!("CARGO_PKG_VERSION");
549    lazy_static::lazy_static! {
550        pub(crate) static ref X_GOOG_API_CLIENT_HEADER: String = {
551            let ac = gaxi::api_header::XGoogApiClient{
552                name:          NAME,
553                version:       VERSION,
554                library_type:  gaxi::api_header::GCCL,
555            };
556            ac.grpc_header_value()
557        };
558    }
559}
560
561/// The set of characters that are percent encoded.
562///
563/// This set is defined at https://cloud.google.com/storage/docs/request-endpoints#encoding:
564///
565/// Encode the following characters when they appear in either the object name
566/// or query string of a request URL:
567///     !, #, $, &, ', (, ), *, +, ,, /, :, ;, =, ?, @, [, ], and space characters.
568const ENCODED_CHARS: percent_encoding::AsciiSet = percent_encoding::CONTROLS
569    .add(b'!')
570    .add(b'#')
571    .add(b'$')
572    .add(b'&')
573    .add(b'\'')
574    .add(b'(')
575    .add(b')')
576    .add(b'*')
577    .add(b'+')
578    .add(b',')
579    .add(b'/')
580    .add(b':')
581    .add(b';')
582    .add(b'=')
583    .add(b'?')
584    .add(b'@')
585    .add(b'[')
586    .add(b']')
587    .add(b' ');
588
589/// Percent encode a string.
590///
591/// To ensure compatibility certain characters need to be encoded when they appear
592/// in either the object name or query string of a request URL.
593pub(crate) fn enc(value: &str) -> String {
594    percent_encoding::utf8_percent_encode(value, &ENCODED_CHARS).to_string()
595}
596
597#[derive(Debug)]
598/// KeyAes256 represents an AES-256 encryption key used with the
599/// Customer-Supplied Encryption Keys (CSEK) feature.
600///
601/// This key must be exactly 32 bytes in length and should be provided in its
602/// raw (unencoded) byte format.
603///
604/// # Examples
605///
606/// Creating a `KeyAes256` instance from a valid byte slice:
607/// ```
608/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
609/// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
610/// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
611/// # Ok::<(), KeyAes256Error>(())
612/// ```
613///
614/// Handling an error for an invalid key length:
615/// ```
616/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
617/// let invalid_key_bytes: &[u8] = b"too_short_key"; // Less than 32 bytes
618/// let result = KeyAes256::new(invalid_key_bytes);
619///
620/// assert!(matches!(result, Err(KeyAes256Error::InvalidLength)));
621/// ```
622pub struct KeyAes256 {
623    key: [u8; 32],
624}
625
626impl KeyAes256 {
627    /// Attempts to create a new [KeyAes256].
628    ///
629    /// This conversion will succeed only if the input slice is exactly 32 bytes long.
630    ///
631    /// # Example
632    /// ```
633    /// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
634    /// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
635    /// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
636    /// # Ok::<(), KeyAes256Error>(())
637    /// ```
638    pub fn new(key: &[u8]) -> std::result::Result<Self, KeyAes256Error> {
639        match key.len() {
640            32 => Ok(Self {
641                key: key[..32].try_into().unwrap(),
642            }),
643            _ => Err(KeyAes256Error::InvalidLength),
644        }
645    }
646}
647
648impl std::convert::From<KeyAes256> for crate::model::CommonObjectRequestParams {
649    fn from(value: KeyAes256) -> Self {
650        crate::model::CommonObjectRequestParams::new()
651            .set_encryption_algorithm("AES256")
652            .set_encryption_key_bytes(value.key.to_vec())
653            .set_encryption_key_sha256_bytes(Sha256::digest(value.key).as_slice().to_owned())
654    }
655}
656
657pub(crate) fn apply_customer_supplied_encryption_headers(
658    builder: reqwest::RequestBuilder,
659    common_object_request_params: &Option<crate::model::CommonObjectRequestParams>,
660) -> reqwest::RequestBuilder {
661    common_object_request_params.iter().fold(builder, |b, v| {
662        b.header(
663            "x-goog-encryption-algorithm",
664            v.encryption_algorithm.clone(),
665        )
666        .header(
667            "x-goog-encryption-key",
668            BASE64_STANDARD.encode(v.encryption_key_bytes.clone()),
669        )
670        .header(
671            "x-goog-encryption-key-sha256",
672            BASE64_STANDARD.encode(v.encryption_key_sha256_bytes.clone()),
673        )
674    })
675}
676
677#[cfg(test)]
678pub(crate) mod tests {
679    use super::*;
680    use gax::retry_result::RetryResult;
681    use std::{sync::Arc, time::Duration};
682    use test_case::test_case;
683
684    type Result = anyhow::Result<()>;
685
686    pub(crate) fn test_builder() -> ClientBuilder {
687        ClientBuilder::new()
688            .with_credentials(auth::credentials::testing::test_credentials())
689            .with_endpoint("http://private.googleapis.com")
690            .with_backoff_policy(
691                gax::exponential_backoff::ExponentialBackoffBuilder::new()
692                    .with_initial_delay(Duration::from_millis(1))
693                    .with_maximum_delay(Duration::from_millis(2))
694                    .build()
695                    .expect("hard coded policy should build correctly"),
696            )
697    }
698
699    /// This is used by the request builder tests.
700    pub(crate) fn test_inner_client(builder: ClientBuilder) -> Arc<StorageInner> {
701        let client = reqwest::Client::new();
702        Arc::new(StorageInner::new(client, builder))
703    }
704
705    /// This is used by the request builder tests.
706    pub(crate) fn create_key_helper() -> (Vec<u8>, String, Vec<u8>, String) {
707        // Make a 32-byte key.
708        let key = vec![b'a'; 32];
709        let key_base64 = BASE64_STANDARD.encode(key.clone());
710
711        let key_sha256 = Sha256::digest(key.clone());
712        let key_sha256_base64 = BASE64_STANDARD.encode(key_sha256);
713        (key, key_base64, key_sha256.to_vec(), key_sha256_base64)
714    }
715
716    #[test]
717    // This tests converting to KeyAes256 from some different types
718    // that can get converted to &[u8].
719    fn test_key_aes_256() -> Result {
720        let v_slice: &[u8] = &[b'c'; 32];
721        KeyAes256::new(v_slice)?;
722
723        let v_vec: Vec<u8> = vec![b'a'; 32];
724        KeyAes256::new(&v_vec)?;
725
726        let v_array: [u8; 32] = [b'a'; 32];
727        KeyAes256::new(&v_array)?;
728
729        let v_bytes: bytes::Bytes = bytes::Bytes::copy_from_slice(&v_array);
730        KeyAes256::new(&v_bytes)?;
731
732        Ok(())
733    }
734
735    #[test_case(&[b'a'; 0]; "no bytes")]
736    #[test_case(&[b'a'; 1]; "not enough bytes")]
737    #[test_case(&[b'a'; 33]; "too many bytes")]
738    fn test_key_aes_256_err(input: &[u8]) {
739        KeyAes256::new(input).unwrap_err();
740    }
741
742    #[test]
743    fn test_key_aes_256_to_control_model_object() -> Result {
744        let (key, _, key_sha256, _) = create_key_helper();
745        let key_aes_256 = KeyAes256::new(&key)?;
746        let params = crate::model::CommonObjectRequestParams::from(key_aes_256);
747        assert_eq!(params.encryption_algorithm, "AES256");
748        assert_eq!(params.encryption_key_bytes, key);
749        assert_eq!(params.encryption_key_sha256_bytes, key_sha256);
750        Ok(())
751    }
752
753    mockall::mock! {
754        #[derive(Debug)]
755        pub RetryThrottler {}
756
757        impl gax::retry_throttler::RetryThrottler for RetryThrottler {
758            fn throttle_retry_attempt(&self) -> bool;
759            fn on_retry_failure(&mut self, flow: &RetryResult);
760            fn on_success(&mut self);
761        }
762    }
763
764    mockall::mock! {
765        #[derive(Debug)]
766        pub RetryPolicy {}
767
768        impl gax::retry_policy::RetryPolicy for RetryPolicy {
769            fn on_error(&self, loop_start: std::time::Instant, attempt_count: u32, idempotent: bool, error: gax::error::Error) -> RetryResult;
770        }
771    }
772
773    mockall::mock! {
774        #[derive(Debug)]
775        pub BackoffPolicy {}
776
777        impl gax::backoff_policy::BackoffPolicy for BackoffPolicy {
778            fn on_failure(&self, loop_start: std::time::Instant, attempt_count: u32) -> std::time::Duration;
779        }
780    }
781
782    mockall::mock! {
783        #[derive(Debug)]
784        pub DownloadResumePolicy {}
785
786        impl crate::download_resume_policy::DownloadResumePolicy for DownloadResumePolicy {
787            fn on_error(&self, query: &crate::download_resume_policy::ResumeQuery, error: gax::error::Error) -> crate::download_resume_policy::ResumeResult;
788        }
789    }
790}