Skip to content

fix(email): bypass Redis cache for Gmail token on init endpoint#2120

Merged
evanhutnik merged 3 commits intomainfrom
evan/no-cache-init
Mar 23, 2026
Merged

fix(email): bypass Redis cache for Gmail token on init endpoint#2120
evanhutnik merged 3 commits intomainfrom
evan/no-cache-init

Conversation

@evanhutnik
Copy link
Copy Markdown
Contributor

Summary

  • Adds a fetch_gmail_access_token_no_cache path that always fetches a fresh Gmail token from the auth service, skipping the Redis cache read but still writing the token back to the cache after fetching.
  • Introduces a dedicated attach_gmail_token_no_cache middleware used only by the /email/init endpoint. All other endpoints continue using the existing cached middleware.
  • Refactors gmail_token_provider.rs to extract shared helpers (fetch_token_from_auth_service, cache_token_in_redis) used by both the cached and no-cache flows.
  • Adds fetch_gmail_access_token_no_cache to the GmailTokenProvider trait and its implementation.

@evanhutnik evanhutnik requested a review from a team as a code owner March 23, 2026 16:55
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

The PR adds a no-cache Gmail token flow: a new fetch_gmail_access_token_no_cache method on GmailTokenProvider and its implementation that always fetches from the auth service and performs a best-effort Redis write. Auth-service fetch and Redis write logic were refactored into shared helpers. The /init route’s middleware was switched to a new attach_gmail_token_no_cache, and a service-layer helper fetch_gmail_token_no_cache was added to call the outbound no-cache path and map failures to HTTP errors.

Poem

🐰 I hop, I bound, to fetch a key,
Straight from auth, no cache for me,
I still tuck it back in Redis hay,
So future bunnies find the way,
Init hums on — a tidy spree.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: bypassing Redis cache for Gmail tokens on the init endpoint.
Description check ✅ Passed The description is directly related to the changeset, detailing the new no-cache path, middleware changes, refactoring, and trait extensions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch evan/no-cache-init

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust/cloud-storage/email_service/src/util/gmail/auth.rs`:
- Around line 108-148: Add tracing instrumentation to ensure consistent
observability: annotate both fetch_gmail_token_no_cache and
fetch_gmail_token_usercontext_response with #[tracing::instrument] (include
relevant fields like user IDs if desired) so their async execution and errors
are captured in traces; update the function signatures to keep attributes above
the async fn declarations and re-run tests to ensure no compile warnings from
unused parameters in the instrumented scope.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c8d6f9ff-180f-4533-8352-e0facddf4514

📥 Commits

Reviewing files that changed from the base of the PR and between 04750b5 and d74bc1f.

📒 Files selected for processing (6)
  • rust/cloud-storage/email/src/inbound/axum/axum_impls.rs
  • rust/cloud-storage/email/src/outbound/gmail_token_provider.rs
  • rust/cloud-storage/email_service/src/api/email/init.rs
  • rust/cloud-storage/email_service/src/api/email/mod.rs
  • rust/cloud-storage/email_service/src/api/middleware/gmail_token.rs
  • rust/cloud-storage/email_service/src/util/gmail/auth.rs

Comment on lines +108 to +148
/// Fetches a user's gmail token directly from the auth service, bypassing the Redis cache.
/// Used by the init endpoint where we always want a fresh token.
pub async fn fetch_gmail_token_no_cache(
user_context: &UserContext,
redis_client: &RedisClient,
auth_service_client: &AuthServiceClient,
) -> Result<String, Response> {
let key = TokenCacheKey::new(
&user_context.fusion_user_id,
&user_context.user_id,
UserProvider::Gmail.as_str(),
);

let conn = redis_client
.inner
.get_multiplexed_async_connection()
.await
.map_err(|e| {
tracing::error!(error=?e, "unable to connect to redis");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
message: "unable to get gmail access token",
}),
)
.into_response()
})?;

email::outbound::fetch_gmail_access_token_no_cache(&key, &conn, auth_service_client)
.await
.map_err(|e| {
tracing::error!(error=?e, "unable to get gmail access token from auth service");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
message: "unable to get gmail access token",
}),
)
.into_response()
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding #[tracing::instrument] for consistency.

The existing fetch_gmail_token_usercontext_response function (lines 82-106) doesn't have #[tracing::instrument], but similar functions like fetch_token_or_delete_on_revocation (line 21) and fetch_and_cache_google_public_keys (line 192) do. For observability consistency, consider adding instrumentation to both this new function and the existing fetch_gmail_token_usercontext_response.

♻️ Suggested instrumentation
+#[tracing::instrument(skip(redis_client, auth_service_client), err)]
 pub async fn fetch_gmail_token_no_cache(
     user_context: &UserContext,
     redis_client: &RedisClient,
     auth_service_client: &AuthServiceClient,
 ) -> Result<String, Response> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Fetches a user's gmail token directly from the auth service, bypassing the Redis cache.
/// Used by the init endpoint where we always want a fresh token.
pub async fn fetch_gmail_token_no_cache(
user_context: &UserContext,
redis_client: &RedisClient,
auth_service_client: &AuthServiceClient,
) -> Result<String, Response> {
let key = TokenCacheKey::new(
&user_context.fusion_user_id,
&user_context.user_id,
UserProvider::Gmail.as_str(),
);
let conn = redis_client
.inner
.get_multiplexed_async_connection()
.await
.map_err(|e| {
tracing::error!(error=?e, "unable to connect to redis");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
message: "unable to get gmail access token",
}),
)
.into_response()
})?;
email::outbound::fetch_gmail_access_token_no_cache(&key, &conn, auth_service_client)
.await
.map_err(|e| {
tracing::error!(error=?e, "unable to get gmail access token from auth service");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
message: "unable to get gmail access token",
}),
)
.into_response()
})
}
/// Fetches a user's gmail token directly from the auth service, bypassing the Redis cache.
/// Used by the init endpoint where we always want a fresh token.
#[tracing::instrument(skip(redis_client, auth_service_client), err)]
pub async fn fetch_gmail_token_no_cache(
user_context: &UserContext,
redis_client: &RedisClient,
auth_service_client: &AuthServiceClient,
) -> Result<String, Response> {
let key = TokenCacheKey::new(
&user_context.fusion_user_id,
&user_context.user_id,
UserProvider::Gmail.as_str(),
);
let conn = redis_client
.inner
.get_multiplexed_async_connection()
.await
.map_err(|e| {
tracing::error!(error=?e, "unable to connect to redis");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
message: "unable to get gmail access token",
}),
)
.into_response()
})?;
email::outbound::fetch_gmail_access_token_no_cache(&key, &conn, auth_service_client)
.await
.map_err(|e| {
tracing::error!(error=?e, "unable to get gmail access token from auth service");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
message: "unable to get gmail access token",
}),
)
.into_response()
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/email_service/src/util/gmail/auth.rs` around lines 108 -
148, Add tracing instrumentation to ensure consistent observability: annotate
both fetch_gmail_token_no_cache and fetch_gmail_token_usercontext_response with
#[tracing::instrument] (include relevant fields like user IDs if desired) so
their async execution and errors are captured in traces; update the function
signatures to keep attributes above the async fn declarations and re-run tests
to ensure no compile warnings from unused parameters in the instrumented scope.


/// Fetches a user's gmail token directly from the auth service, bypassing the Redis cache.
/// Used by the init endpoint where we always want a fresh token.
pub async fn fetch_gmail_token_no_cache(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tracing

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust/cloud-storage/email_service/src/api/middleware/gmail_token.rs`:
- Around line 29-35: The tracing instrumentation on the Result-returning
function attach_gmail_token_no_cache should include error capture; update the
attribute on the function declaration (#[tracing::instrument(...)] applied to
attach_gmail_token_no_cache) to add err alongside the existing skip list (e.g.,
#[tracing::instrument(skip(ctx, user_context, req, next), err)]) so tracing
records error outcomes from the function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb109526-59fe-41e0-b39e-8d4b85cc3941

📥 Commits

Reviewing files that changed from the base of the PR and between d74bc1f and 110911d.

📒 Files selected for processing (3)
  • rust/cloud-storage/email/src/outbound/gmail_token_provider.rs
  • rust/cloud-storage/email_service/src/api/middleware/gmail_token.rs
  • rust/cloud-storage/email_service/src/util/gmail/auth.rs

Comment on lines +29 to +35
#[tracing::instrument(skip(ctx, user_context, req, next))]
pub(in crate::api) async fn attach_gmail_token_no_cache(
State(ctx): State<ApiContext>,
user_context: Extension<UserContext>,
mut req: Request,
next: Next,
) -> Result<Response, Response> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add err to #[tracing::instrument] for Result-returning function.

The function returns Result<Response, Response>, so per coding guidelines, the #[tracing::instrument] attribute should include err to capture error outcomes in traces.

🔧 Proposed fix
-#[tracing::instrument(skip(ctx, user_context, req, next))]
+#[tracing::instrument(skip(ctx, user_context, req, next), err)]
 pub(in crate::api) async fn attach_gmail_token_no_cache(

As per coding guidelines: "Include err when adding the tracing::instrument attribute to functions that return Result."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[tracing::instrument(skip(ctx, user_context, req, next))]
pub(in crate::api) async fn attach_gmail_token_no_cache(
State(ctx): State<ApiContext>,
user_context: Extension<UserContext>,
mut req: Request,
next: Next,
) -> Result<Response, Response> {
#[tracing::instrument(skip(ctx, user_context, req, next), err)]
pub(in crate::api) async fn attach_gmail_token_no_cache(
State(ctx): State<ApiContext>,
user_context: Extension<UserContext>,
mut req: Request,
next: Next,
) -> Result<Response, Response> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/email_service/src/api/middleware/gmail_token.rs` around
lines 29 - 35, The tracing instrumentation on the Result-returning function
attach_gmail_token_no_cache should include error capture; update the attribute
on the function declaration (#[tracing::instrument(...)] applied to
attach_gmail_token_no_cache) to add err alongside the existing skip list (e.g.,
#[tracing::instrument(skip(ctx, user_context, req, next), err)]) so tracing
records error outcomes from the function.

@evanhutnik evanhutnik merged commit 8753994 into main Mar 23, 2026
41 checks passed
@evanhutnik evanhutnik deleted the evan/no-cache-init branch March 23, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants