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::streaming_source::Payload;
21use auth::credentials::CacheableResource;
22use base64::Engine;
23use base64::prelude::BASE64_STANDARD;
24use http::Extensions;
25use std::sync::Arc;
26
27/// Implements a client for the Cloud Storage API.
28///
29/// # Example
30/// ```
31/// # async fn sample() -> anyhow::Result<()> {
32/// # use google_cloud_storage::client::Storage;
33/// let client = Storage::builder().build().await?;
34/// // use `client` to make requests to Cloud Storage.
35/// # 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<S = crate::storage::transport::Storage>
90where
91    S: crate::storage::stub::Storage + 'static,
92{
93    stub: std::sync::Arc<S>,
94    options: RequestOptions,
95}
96
97#[derive(Clone, Debug)]
98pub(crate) struct StorageInner {
99    pub client: reqwest::Client,
100    pub cred: auth::credentials::Credentials,
101    pub endpoint: String,
102    pub options: RequestOptions,
103}
104
105impl Storage {
106    /// Returns a builder for [Storage].
107    ///
108    /// # Example
109    /// ```
110    /// # use google_cloud_storage::client::Storage;
111    /// # async fn sample() -> anyhow::Result<()> {
112    /// let client = Storage::builder().build().await?;
113    /// # Ok(()) }
114    /// ```
115    pub fn builder() -> ClientBuilder {
116        ClientBuilder::new()
117    }
118}
119
120impl<S> Storage<S>
121where
122    S: crate::storage::stub::Storage + 'static,
123{
124    /// Creates a new client from the provided stub.
125    ///
126    /// The most common case for calling this function is in tests mocking the
127    /// client's behavior.
128    pub fn from_stub(stub: S) -> Self
129    where
130        S: super::stub::Storage + 'static,
131    {
132        Self {
133            stub: std::sync::Arc::new(stub),
134            options: RequestOptions::new(),
135        }
136    }
137
138    /// Write an object with data from any data source.
139    ///
140    /// # Example
141    /// ```
142    /// # use google_cloud_storage::client::Storage;
143    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
144    /// let response = client
145    ///     .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
146    ///     .send_buffered()
147    ///     .await?;
148    /// println!("response details={response:?}");
149    /// # Ok(()) }
150    /// ```
151    ///
152    /// # Example
153    /// ```
154    /// # use google_cloud_storage::client::Storage;
155    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
156    /// let response = client
157    ///     .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
158    ///     .send_unbuffered()
159    ///     .await?;
160    /// println!("response details={response:?}");
161    /// # Ok(()) }
162    /// ```
163    ///
164    /// You can use many different types as the payload. For example, a string,
165    /// a [bytes::Bytes], a [tokio::fs::File], or a custom type that implements
166    /// the [StreamingSource] trait.
167    ///
168    /// If your data source also implements [Seek], prefer [send_unbuffered()]
169    /// to start the write. Otherwise use [send_buffered()].
170    ///
171    /// # Parameters
172    /// * `bucket` - the bucket name containing the object. In
173    ///   `projects/_/buckets/{bucket_id}` format.
174    /// * `object` - the object name.
175    /// * `payload` - the object data.
176    ///
177    /// [Seek]: crate::streaming_source::Seek
178    /// [StreamingSource]: crate::streaming_source::StreamingSource
179    /// [send_buffered()]: crate::builder::storage::WriteObject::send_buffered
180    /// [send_unbuffered()]: crate::builder::storage::WriteObject::send_unbuffered
181    pub fn write_object<B, O, T, P>(&self, bucket: B, object: O, payload: T) -> WriteObject<P, S>
182    where
183        B: Into<String>,
184        O: Into<String>,
185        T: Into<Payload<P>>,
186    {
187        WriteObject::new(
188            self.stub.clone(),
189            bucket,
190            object,
191            payload,
192            self.options.clone(),
193        )
194    }
195
196    /// Reads the contents of an object.
197    ///
198    /// # Example
199    /// ```
200    /// # use google_cloud_storage::client::Storage;
201    /// # async fn sample(client: &Storage) -> anyhow::Result<()> {
202    /// let mut resp = client
203    ///     .read_object("projects/_/buckets/my-bucket", "my-object")
204    ///     .send()
205    ///     .await?;
206    /// let mut contents = Vec::new();
207    /// while let Some(chunk) = resp.next().await.transpose()? {
208    ///   contents.extend_from_slice(&chunk);
209    /// }
210    /// println!("object contents={:?}", bytes::Bytes::from_owner(contents));
211    /// # Ok(()) }
212    /// ```
213    ///
214    /// # Parameters
215    /// * `bucket` - the bucket name containing the object. In
216    ///   `projects/_/buckets/{bucket_id}` format.
217    /// * `object` - the object name.
218    pub fn read_object<B, O>(&self, bucket: B, object: O) -> ReadObject<S>
219    where
220        B: Into<String>,
221        O: Into<String>,
222    {
223        ReadObject::new(self.stub.clone(), bucket, object, self.options.clone())
224    }
225}
226
227impl Storage {
228    pub(crate) fn new(builder: ClientBuilder) -> gax::client_builder::Result<Self> {
229        use gax::client_builder::Error;
230        let client = reqwest::Client::builder()
231            // Disable all automatic decompression. These could be enabled by users by enabling
232            // the corresponding features flags, but we will not be able to tell whether this
233            // has happened.
234            .no_brotli()
235            .no_deflate()
236            .no_gzip()
237            .no_zstd()
238            .build()
239            .map_err(Error::transport)?;
240        let mut builder = builder;
241        let cred = if let Some(c) = builder.credentials {
242            c
243        } else {
244            auth::credentials::Builder::default()
245                .build()
246                .map_err(Error::cred)?
247        };
248        let endpoint = builder
249            .endpoint
250            .unwrap_or_else(|| self::DEFAULT_HOST.to_string());
251        builder.credentials = Some(cred);
252        builder.endpoint = Some(endpoint);
253        let inner = Arc::new(StorageInner::new(client, builder));
254        let options = inner.options.clone();
255        let stub = crate::storage::transport::Storage::new(inner);
256        Ok(Self { stub, options })
257    }
258}
259
260impl StorageInner {
261    /// Builds a client assuming `config.cred` and `config.endpoint` are initialized, panics otherwise.
262    pub(self) fn new(client: reqwest::Client, builder: ClientBuilder) -> Self {
263        Self {
264            client,
265            cred: builder
266                .credentials
267                .expect("StorageInner assumes the credentials are initialized"),
268            endpoint: builder
269                .endpoint
270                .expect("StorageInner assumes the endpoint is initialized"),
271            options: builder.default_options,
272        }
273    }
274
275    // Helper method to apply authentication headers to the request builder.
276    pub async fn apply_auth_headers(
277        &self,
278        builder: reqwest::RequestBuilder,
279    ) -> crate::Result<reqwest::RequestBuilder> {
280        let cached_auth_headers = self
281            .cred
282            .headers(Extensions::new())
283            .await
284            .map_err(Error::authentication)?;
285
286        let auth_headers = match cached_auth_headers {
287            CacheableResource::New { data, .. } => data,
288            CacheableResource::NotModified => {
289                unreachable!("headers are not cached");
290            }
291        };
292
293        let builder = builder.headers(auth_headers);
294        Ok(builder)
295    }
296}
297
298/// A builder for [Storage].
299///
300/// ```
301/// # use google_cloud_storage::client::Storage;
302/// # async fn sample() -> anyhow::Result<()> {
303/// let builder = Storage::builder();
304/// let client = builder
305///     .with_endpoint("https://storage.googleapis.com")
306///     .build()
307///     .await?;
308/// # Ok(()) }
309/// ```
310pub struct ClientBuilder {
311    pub(crate) endpoint: Option<String>,
312    pub(crate) credentials: Option<auth::credentials::Credentials>,
313    // Default options for requests.
314    pub(crate) default_options: RequestOptions,
315}
316
317impl ClientBuilder {
318    pub(crate) fn new() -> Self {
319        Self {
320            endpoint: None,
321            credentials: None,
322            default_options: RequestOptions::new(),
323        }
324    }
325
326    /// Creates a new client.
327    ///
328    /// # Example
329    /// ```
330    /// # use google_cloud_storage::client::Storage;
331    /// # async fn sample() -> anyhow::Result<()> {
332    /// let client = Storage::builder().build().await?;
333    /// # Ok(()) }
334    /// ```
335    pub async fn build(self) -> gax::client_builder::Result<Storage> {
336        Storage::new(self)
337    }
338
339    /// Sets the endpoint.
340    ///
341    /// # Example
342    /// ```
343    /// # use google_cloud_storage::client::Storage;
344    /// # async fn sample() -> anyhow::Result<()> {
345    /// let client = Storage::builder()
346    ///     .with_endpoint("https://private.googleapis.com")
347    ///     .build()
348    ///     .await?;
349    /// # Ok(()) }
350    /// ```
351    pub fn with_endpoint<V: Into<String>>(mut self, v: V) -> Self {
352        self.endpoint = Some(v.into());
353        self
354    }
355
356    /// Configures the authentication credentials.
357    ///
358    /// Google Cloud Storage requires authentication for most buckets. Use this
359    /// method to change the credentials used by the client. More information
360    /// about valid credentials types can be found in the [google-cloud-auth]
361    /// crate documentation.
362    ///
363    /// # Example
364    /// ```
365    /// # use google_cloud_storage::client::Storage;
366    /// # async fn sample() -> anyhow::Result<()> {
367    /// use auth::credentials::mds;
368    /// let client = Storage::builder()
369    ///     .with_credentials(
370    ///         mds::Builder::default()
371    ///             .with_scopes(["https://www.googleapis.com/auth/cloud-platform.read-only"])
372    ///             .build()?)
373    ///     .build()
374    ///     .await?;
375    /// # Ok(()) }
376    /// ```
377    ///
378    /// [google-cloud-auth]: https://docs.rs/google-cloud-auth
379    pub fn with_credentials<V: Into<auth::credentials::Credentials>>(mut self, v: V) -> Self {
380        self.credentials = Some(v.into());
381        self
382    }
383
384    /// Configure the retry policy.
385    ///
386    /// The client libraries can automatically retry operations that fail. The
387    /// retry policy controls what errors are considered retryable, sets limits
388    /// on the number of attempts or the time trying to make attempts.
389    ///
390    /// # Example
391    /// ```
392    /// # use google_cloud_storage::client::Storage;
393    /// # async fn sample() -> anyhow::Result<()> {
394    /// use gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
395    /// let client = Storage::builder()
396    ///     .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
397    ///     .build()
398    ///     .await?;
399    /// # Ok(()) }
400    /// ```
401    pub fn with_retry_policy<V: Into<gax::retry_policy::RetryPolicyArg>>(mut self, v: V) -> Self {
402        self.default_options.retry_policy = v.into().into();
403        self
404    }
405
406    /// Configure the retry backoff policy.
407    ///
408    /// The client libraries can automatically retry operations that fail. The
409    /// backoff policy controls how long to wait in between retry attempts.
410    ///
411    /// # Example
412    /// ```
413    /// # use google_cloud_storage::client::Storage;
414    /// # async fn sample() -> anyhow::Result<()> {
415    /// use gax::exponential_backoff::ExponentialBackoff;
416    /// use std::time::Duration;
417    /// let policy = ExponentialBackoff::default();
418    /// let client = Storage::builder()
419    ///     .with_backoff_policy(policy)
420    ///     .build()
421    ///     .await?;
422    /// # Ok(()) }
423    /// ```
424    pub fn with_backoff_policy<V: Into<gax::backoff_policy::BackoffPolicyArg>>(
425        mut self,
426        v: V,
427    ) -> Self {
428        self.default_options.backoff_policy = v.into().into();
429        self
430    }
431
432    /// Configure the retry throttler.
433    ///
434    /// Advanced applications may want to configure a retry throttler to
435    /// [Address Cascading Failures] and when [Handling Overload] conditions.
436    /// The client libraries throttle their retry loop, using a policy to
437    /// control the throttling algorithm. Use this method to fine tune or
438    /// customize the default retry throtler.
439    ///
440    /// [Handling Overload]: https://sre.google/sre-book/handling-overload/
441    /// [Addressing Cascading Failures]: https://sre.google/sre-book/addressing-cascading-failures/
442    ///
443    /// # Example
444    /// ```
445    /// # use google_cloud_storage::client::Storage;
446    /// # async fn sample() -> anyhow::Result<()> {
447    /// use gax::retry_throttler::AdaptiveThrottler;
448    /// let client = Storage::builder()
449    ///     .with_retry_throttler(AdaptiveThrottler::default())
450    ///     .build()
451    ///     .await?;
452    /// # Ok(()) }
453    /// ```
454    pub fn with_retry_throttler<V: Into<gax::retry_throttler::RetryThrottlerArg>>(
455        mut self,
456        v: V,
457    ) -> Self {
458        self.default_options.retry_throttler = v.into().into();
459        self
460    }
461
462    /// Sets the payload size threshold to switch from single-shot to resumable uploads.
463    ///
464    /// # Example
465    /// ```
466    /// # use google_cloud_storage::client::Storage;
467    /// # async fn sample() -> anyhow::Result<()> {
468    /// let client = Storage::builder()
469    ///     .with_resumable_upload_threshold(0_usize) // Forces a resumable upload.
470    ///     .build()
471    ///     .await?;
472    /// let response = client
473    ///     .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
474    ///     .send_buffered()
475    ///     .await?;
476    /// println!("response details={response:?}");
477    /// # Ok(()) }
478    /// ```
479    ///
480    /// The client library can write objects using [single-shot] or [resumable]
481    /// uploads. For small objects, single-shot uploads offer better
482    /// performance, as they require a single HTTP transfer. For larger objects,
483    /// the additional request latency is not significant, and resumable uploads
484    /// offer better recovery on errors.
485    ///
486    /// The library automatically selects resumable uploads when the payload is
487    /// equal to or larger than this option. For smaller writes the client
488    /// library uses single-shot uploads.
489    ///
490    /// The exact threshold depends on where the application is deployed and
491    /// destination bucket location with respect to where the application is
492    /// running. The library defaults should work well in most cases, but some
493    /// applications may benefit from fine-tuning.
494    ///
495    /// [single-shot]: https://cloud.google.com/storage/docs/uploading-objects
496    /// [resumable]: https://cloud.google.com/storage/docs/resumable-uploads
497    pub fn with_resumable_upload_threshold<V: Into<usize>>(mut self, v: V) -> Self {
498        self.default_options.resumable_upload_threshold = v.into();
499        self
500    }
501
502    /// Changes the buffer size for some resumable uploads.
503    ///
504    /// # Example
505    /// ```
506    /// # use google_cloud_storage::client::Storage;
507    /// # async fn sample() -> anyhow::Result<()> {
508    /// let client = Storage::builder()
509    ///     .with_resumable_upload_buffer_size(32 * 1024 * 1024_usize)
510    ///     .build()
511    ///     .await?;
512    /// let response = client
513    ///     .write_object("projects/_/buckets/my-bucket", "my-object", "hello world")
514    ///     .send_buffered()
515    ///     .await?;
516    /// println!("response details={response:?}");
517    /// # Ok(()) }
518    /// ```
519    ///
520    /// When performing [resumable uploads] from sources without [Seek] the
521    /// client library needs to buffer data in memory until it is persisted by
522    /// the service. Otherwise the data would be lost if the upload is
523    /// interrupted. Applications may want to tune this buffer size:
524    ///
525    /// - Use smaller buffer sizes to support more concurrent writes in the
526    ///   same application.
527    /// - Use larger buffer sizes for better throughput. Sending many small
528    ///   buffers stalls the writer until the client receives a successful
529    ///   response from the service.
530    ///
531    /// Keep in mind that there are diminishing returns on using larger buffers.
532    ///
533    /// [resumable uploads]: https://cloud.google.com/storage/docs/resumable-uploads
534    /// [Seek]: crate::streaming_source::Seek
535    pub fn with_resumable_upload_buffer_size<V: Into<usize>>(mut self, v: V) -> Self {
536        self.default_options.resumable_upload_buffer_size = v.into();
537        self
538    }
539
540    /// Configure the resume policy for object reads.
541    ///
542    /// The Cloud Storage client library can automatically resume a read request
543    /// that is interrupted by a transient error. Applications may want to
544    /// limit the number of read attempts, or may wish to expand the type
545    /// of errors treated as retryable.
546    ///
547    /// # Example
548    /// ```
549    /// # use google_cloud_storage::client::Storage;
550    /// # async fn sample() -> anyhow::Result<()> {
551    /// use google_cloud_storage::read_resume_policy::{AlwaysResume, ReadResumePolicyExt};
552    /// let client = Storage::builder()
553    ///     .with_read_resume_policy(AlwaysResume.with_attempt_limit(3))
554    ///     .build()
555    ///     .await?;
556    /// # Ok(()) }
557    /// ```
558    pub fn with_read_resume_policy<V>(mut self, v: V) -> Self
559    where
560        V: ReadResumePolicy + 'static,
561    {
562        self.default_options.read_resume_policy = Arc::new(v);
563        self
564    }
565}
566
567/// The default host used by the service.
568const DEFAULT_HOST: &str = "https://storage.googleapis.com";
569
570pub(crate) mod info {
571    const NAME: &str = env!("CARGO_PKG_NAME");
572    const VERSION: &str = env!("CARGO_PKG_VERSION");
573    lazy_static::lazy_static! {
574        pub(crate) static ref X_GOOG_API_CLIENT_HEADER: String = {
575            let ac = gaxi::api_header::XGoogApiClient{
576                name:          NAME,
577                version:       VERSION,
578                library_type:  gaxi::api_header::GCCL,
579            };
580            ac.grpc_header_value()
581        };
582    }
583}
584
585/// The set of characters that are percent encoded.
586///
587/// This set is defined at https://cloud.google.com/storage/docs/request-endpoints#encoding:
588///
589/// Encode the following characters when they appear in either the object name
590/// or query string of a request URL:
591///     !, #, $, &, ', (, ), *, +, ,, /, :, ;, =, ?, @, [, ], and space characters.
592const ENCODED_CHARS: percent_encoding::AsciiSet = percent_encoding::CONTROLS
593    .add(b'!')
594    .add(b'#')
595    .add(b'$')
596    .add(b'&')
597    .add(b'\'')
598    .add(b'(')
599    .add(b')')
600    .add(b'*')
601    .add(b'+')
602    .add(b',')
603    .add(b'/')
604    .add(b':')
605    .add(b';')
606    .add(b'=')
607    .add(b'?')
608    .add(b'@')
609    .add(b'[')
610    .add(b']')
611    .add(b' ');
612
613/// Percent encode a string.
614///
615/// To ensure compatibility certain characters need to be encoded when they appear
616/// in either the object name or query string of a request URL.
617pub(crate) fn enc(value: &str) -> String {
618    percent_encoding::utf8_percent_encode(value, &ENCODED_CHARS).to_string()
619}
620
621pub(crate) fn apply_customer_supplied_encryption_headers(
622    builder: reqwest::RequestBuilder,
623    common_object_request_params: &Option<crate::model::CommonObjectRequestParams>,
624) -> reqwest::RequestBuilder {
625    common_object_request_params.iter().fold(builder, |b, v| {
626        b.header(
627            "x-goog-encryption-algorithm",
628            v.encryption_algorithm.clone(),
629        )
630        .header(
631            "x-goog-encryption-key",
632            BASE64_STANDARD.encode(v.encryption_key_bytes.clone()),
633        )
634        .header(
635            "x-goog-encryption-key-sha256",
636            BASE64_STANDARD.encode(v.encryption_key_sha256_bytes.clone()),
637        )
638    })
639}
640
641#[cfg(test)]
642pub(crate) mod tests {
643    use super::*;
644    use gax::retry_result::RetryResult;
645    use gax::retry_state::RetryState;
646    use std::{sync::Arc, time::Duration};
647
648    pub(crate) fn test_builder() -> ClientBuilder {
649        ClientBuilder::new()
650            .with_credentials(auth::credentials::testing::test_credentials())
651            .with_endpoint("http://private.googleapis.com")
652            .with_backoff_policy(
653                gax::exponential_backoff::ExponentialBackoffBuilder::new()
654                    .with_initial_delay(Duration::from_millis(1))
655                    .with_maximum_delay(Duration::from_millis(2))
656                    .build()
657                    .expect("hard coded policy should build correctly"),
658            )
659    }
660
661    /// This is used by the request builder tests.
662    pub(crate) fn test_inner_client(builder: ClientBuilder) -> Arc<StorageInner> {
663        let client = reqwest::Client::new();
664        Arc::new(StorageInner::new(client, builder))
665    }
666
667    mockall::mock! {
668        #[derive(Debug)]
669        pub RetryThrottler {}
670
671        impl gax::retry_throttler::RetryThrottler for RetryThrottler {
672            fn throttle_retry_attempt(&self) -> bool;
673            fn on_retry_failure(&mut self, flow: &RetryResult);
674            fn on_success(&mut self);
675        }
676    }
677
678    mockall::mock! {
679        #[derive(Debug)]
680        pub RetryPolicy {}
681
682        impl gax::retry_policy::RetryPolicy for RetryPolicy {
683            fn on_error(&self, state: &RetryState, error: gax::error::Error) -> RetryResult;
684        }
685    }
686
687    mockall::mock! {
688        #[derive(Debug)]
689        pub BackoffPolicy {}
690
691        impl gax::backoff_policy::BackoffPolicy for BackoffPolicy {
692            fn on_failure(&self, loop_start: std::time::Instant, attempt_count: u32) -> std::time::Duration;
693        }
694    }
695
696    mockall::mock! {
697        #[derive(Debug)]
698        pub ReadResumePolicy {}
699
700        impl crate::read_resume_policy::ReadResumePolicy for ReadResumePolicy {
701            fn on_error(&self, query: &crate::read_resume_policy::ResumeQuery, error: gax::error::Error) -> crate::read_resume_policy::ResumeResult;
702        }
703    }
704}