google-cloud-rust icon indicating copy to clipboard operation
google-cloud-rust copied to clipboard

How should google-cloud-auth CacheableResource<> be used?

Open sxlijin opened this issue 3 months ago • 4 comments

Excited to have an official Rust SDK, so thanks!

I'm trying to figure out how to use the CacheableResouce<> type in google-cloud-auth and have no idea how I'm supposed to use the returned EntityTag to cache the returned auth headers. Is it deliberate that there's no user-visible timeout on the auth headers? Or is there an API that I'm supposed to be able to use the EntityTag with to determine if my headers are still sufficiently recent (I don't see anything on CredentialsProvider that I can use)?

Links:

  • https://docs.rs/google-cloud-auth/1.0.0/google_cloud_auth/credentials/struct.Credentials.html
  • https://docs.rs/google-cloud-auth/1.0.0/google_cloud_auth/credentials/trait.CredentialsProvider.html
  • https://docs.rs/google-cloud-auth/1.0.0/google_cloud_auth/credentials/enum.CacheableResource.html

sxlijin avatar Sep 19 '25 01:09 sxlijin

Excited to have an official Rust SDK, so thanks!

We are too.

I'm trying to figure out how to use the CacheableResource<> type in google-cloud-auth and have no idea how I'm supposed to use the returned EntityTag to cache the returned auth headers.

Yes, we are missing some user-guides / tutorials for auth.

Is it deliberate that there's no user-visible timeout on the auth headers?

Yes.

Or is there an API that I'm supposed to be able to use the EntityTag with to determine if my headers are still sufficiently recent (I don't see anything on CredentialsProvider that I can use)?

The credentials will automatically refresh any token in the background. And they automatically cache the headers until the token is about to expire. If you are going to use the headers as they are, without any additional transformations, then you can ignore the EntityTag and pass the headers to your HTTP or gRPC client.

struct MyClient {
   creds: google_cloud_auth::credentials::Credentials,
   inner: ... some client that uses http::Headers directly ...
}

impl MyClient {
    pub async fn thing(&self) -> Result<Thing> {
       let headers = match self.creds.headers(http::Extensions())? {
           CacheableResource::NotModified => unreachable!(),
           CacheableResource::New { entity_tag, data } => data,
       };
       self.inner.do_some_call_using_headers(headers)
   }

If you perform some additional computations based on the headers, then the entity tag can help you avoid the recomputation. The pseudo-code would be something like:

use google_cloud_auth::credentials::CacheableResource;

struct MyClient {
   inner: google_cloud_auth::credentials::Credentials,
   cached: Option<CacheableResource<ExpensiveThingDerivedFromHeaders>>,
   inner: ... some client that uses ExpensiveThing directly ...
}

impl MyClient {
    pub async fn thing(&mut self) -> Result<Thing> {
       let extensions = self.cached.iter().fold(http::Extensions::new(), |e, t| { e.insert(t); e });
       let expensive : ExpensiveThingDerivedFromHeaders = match self.creds.headers()? {
           CacheableResource::NotModified => self.cached().clone().unwrap().data,
           CacheableResource::New { entity_tag, data } => {
               let ex = compute_expensive_thing_from_headers(data);
               self.cached = Some(CacheableResource::New { entity_tag, ex.clone() });
               ex
           },
       };
       inner.do_some_call_using_expensive(expensive)
   }

coryan avatar Sep 19 '25 12:09 coryan

I've been using, but been quite nervous about, using unreachable!() for this code path. Could you also add some explicit clarification/guarantees on under what conditions a NotModified won't be returned?

e.g. from your sample code, I might imagine it's something like "when the Extensions value contains no EntityKey from the type definition in this library, we will never return a NotModified".

(and I'm also very very happy this exists, to replace the hacky code we had to use before now - also appreciate the huge amount of work that's obviously gone into it)

fiadliel avatar Sep 19 '25 13:09 fiadliel

I've been using, but been quite nervous about, using unreachable!() for this code path. Could you also add some explicit clarification/guarantees on under what conditions a NotModified won't be returned?

Yes. In addition, I think we should provide an extension trait that simplifies the API and makes this clear via the type system. Something like:

pub trait CredentialsExt {
    async fn cached_headers(&self) -> Result<HeaderMap> { .... }

(and I'm also very very happy this exists, to replace the hacky code we had to use before now - also appreciate the huge amount of work that's obviously gone into it)

Thanks for saying that.

coryan avatar Sep 19 '25 15:09 coryan

The credentials will automatically refresh any token in the background. And they automatically cache the headers until the token is about to expire.

Got it - this is the bit I was looking for that wasn't present in the docs.

Yes. In addition, I think we should provide an extension trait that simplifies the API and makes this clear via the type system.

It might be the case that CacheableResource<> shouldn't be exposed to us, as external users? At least in its current shape, NotModified seems like it must be used in concert with some other state tracking which AFAICT as the client we're not supposed to be doing.

sxlijin avatar Sep 19 '25 17:09 sxlijin