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