athena_rs 0.83.0

Database gateway API
Documentation
//! Cache Check Module
//!
//! This module provides functionality to check and retrieve cached responses based on cache control headers.

use crate::AppState;
use crate::utils::redis_client::GLOBAL_REDIS;
use actix_web::{HttpRequest, HttpResponse, web::Data};
use serde_json::{Value, json};

/// ## Get Cached Response
///
/// This function retrieves a cached response for a given cache key.
///
/// ### Parameters
///
/// - `app_state`: A `Data<AppState>` instance containing the shared cache.
/// - `cache_key`: A string slice representing the key to look up in the cache.
///
/// ### Returns
///
/// - `Option<HttpResponse>`: Returns an `HttpResponse` containing the cached data if found, otherwise `None`.
///
/// ### Example
///
/// ```rust,no_run
/// # use actix_web::web::Data;
/// # use athena_rs::api::cache::check::get_cached_response;
/// # use athena_rs::AppState;
/// # use athena_rs::drivers::postgresql::sqlx_driver::PostgresClientRegistry;
/// # use moka::future::Cache;
/// # use reqwest::Client;
/// # use serde_json::json;
/// # use std::sync::Arc;
/// # async fn doc_example() {
/// #     let cache = Cache::builder().build();
/// #     let immortal = Cache::builder().build();
/// #     let jdbc_pool_cache = Arc::new(Cache::builder().max_capacity(64).build());
/// #     let app_state = AppState {
/// #         cache: Arc::new(cache),
/// #         immortal_cache: Arc::new(immortal),
/// #         client: Client::new(),
/// #         pg_registry: Arc::new(PostgresClientRegistry::empty()),
/// #         jdbc_pool_cache,
/// #         gateway_force_camel_case_to_snake_case: false,
/// #         pipeline_registry: None,
/// #         logging_client_name: None,
/// #         gateway_auth_client_name: None,
/// #         prometheus_metrics_enabled: false,
/// #     };
/// #     let cache = Data::new(app_state);
/// let response = get_cached_response(cache, "some_cache_key").await;
/// #     let _ = response;
/// # }
/// ```
pub async fn get_cached_response(
    app_state: Data<AppState>,
    cache_key: &str,
) -> Option<HttpResponse> {
    if let Some(cached_response) = app_state.cache.get(cache_key).await {
        return Some(HttpResponse::Ok().json(cached_response.clone()));
    }
    // Try Redis on local miss
    if let Some(redis) = GLOBAL_REDIS.get() {
        if let Ok(value) = redis.lock().await.get(cache_key).await {
            if !value.is_null() {
                app_state
                    .cache
                    .insert(cache_key.to_string(), value.clone())
                    .await;
                return Some(HttpResponse::Ok().json(value));
            }
        }
    }
    None
}

/// ## Check Cache Control and Get Response
///
/// This function checks if the "Cache-Control" header is set to "no-cache" and retrieves the cached response if not.
///
/// ### Parameters
///
/// - `req`: A reference to the `HttpRequest` object.
/// - `app_state`: A `Data<AppState>` instance containing the shared cache.
/// - `cache_key`: A string slice representing the key to look up in the cache.
///
/// ### Returns
///
/// - `Option<HttpResponse>`: Returns an `HttpResponse` containing the cached data if found and cache control is not "no-cache", otherwise `None`.
///
/// ### Example
///
/// ```rust,no_run
/// # use actix_web::http::header::CACHE_CONTROL;
/// # use actix_web::test::TestRequest;
/// # use actix_web::web::Data;
/// # use athena_rs::api::cache::check::check_cache_control_and_get_response;
/// # use athena_rs::AppState;
/// # use athena_rs::drivers::postgresql::sqlx_driver::PostgresClientRegistry;
/// # use moka::future::Cache;
/// # use reqwest::Client;
/// # use serde_json::json;
/// # use std::sync::Arc;
/// # async fn doc_example() {
/// #     let cache = Cache::builder().build();
/// #     let immortal = Cache::builder().build();
/// #     let jdbc_pool_cache = Arc::new(Cache::builder().max_capacity(64).build());
/// #     let app_state = AppState {
/// #         cache: Arc::new(cache),
/// #         immortal_cache: Arc::new(immortal),
/// #         client: Client::new(),
/// #         pg_registry: Arc::new(PostgresClientRegistry::empty()),
/// #         jdbc_pool_cache,
/// #         gateway_force_camel_case_to_snake_case: false,
/// #         pipeline_registry: None,
/// #         logging_client_name: None,
/// #         gateway_auth_client_name: None,
/// #         prometheus_metrics_enabled: false,
/// #     };
/// #     let cache = Data::new(app_state);
/// #     let req = TestRequest::default()
/// #         .insert_header((CACHE_CONTROL, "max-age=0"))
/// #         .to_http_request();
/// let response = check_cache_control_and_get_response(&req, cache, "some_cache_key").await;
/// #     let _ = response;
/// # }
/// ```
pub async fn check_cache_control_and_get_response(
    req: &HttpRequest,
    app_state: Data<AppState>,
    cache_key: &str,
) -> Option<HttpResponse> {
    if let Some(cache_control_header) = req.headers().get("Cache-Control") {
        if let Ok(cache_control_value) = cache_control_header.to_str() {
            if cache_control_value.contains("no-cache") {
                return None;
            }
        }
    }

    if let Some(cached_response) = app_state.cache.get(cache_key).await {
        return Some(HttpResponse::Ok().json(json!({
            "success": true,
            "data": cached_response.clone()
        })));
    }
    None
}

/// ## Check Cache Control and Get Response V2
///
/// This function checks if the "Cache-Control" header is set to "no-cache" and retrieves the cached response if not.
///
/// ### Parameters
///
pub async fn check_cache_control_and_get_response_v2(
    req: &HttpRequest,
    app_state: Data<AppState>,
    cache_key: &str,
) -> Option<HttpResponse> {
    if let Some(cache_control_header) = req.headers().get("Cache-Control") {
        if let Ok(cache_control_value) = cache_control_header.to_str() {
            if cache_control_value.contains("no-cache") {
                return None;
            }
        }
    }
    // Prefer local cache; on miss, try Redis
    if let Some(cached_response) = app_state.cache.get(cache_key).await {
        // Removed verbose logging of entire cached response
        let response_data: Value = cached_response.clone();

        // If the response is an array with a single item, return just that item
        if let Value::Array(arr) = &response_data {
            if arr.len() == 1 {
                return Some(HttpResponse::Ok().json(&arr[0]));
            }
        }

        return Some(HttpResponse::Ok().json(response_data));
    }
    // local miss: try Redis
    if let Some(redis) = GLOBAL_REDIS.get() {
        if let Ok(value) = redis.lock().await.get(cache_key).await {
            if !value.is_null() {
                // backfill local cache and return
                app_state
                    .cache
                    .insert(cache_key.to_string(), value.clone())
                    .await;

                if let Value::Array(arr) = &value {
                    if arr.len() == 1 {
                        return Some(HttpResponse::Ok().json(&arr[0]));
                    }
                }
                return Some(HttpResponse::Ok().json(value));
            }
        }
    }
    None
}