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}