athena_rs 2.9.1

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;
/// # use std::time::Instant;
/// # 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(),
/// #         process_start_time_seconds: 0,
/// #         process_started_at: Instant::now(),
/// #         pg_registry: Arc::new(PostgresClientRegistry::empty()),
/// #         jdbc_pool_cache,
/// #         gateway_force_camel_case_to_snake_case: false,
/// #         gateway_auto_cast_uuid_filter_values_to_text: true,
/// #         gateway_allow_schema_names_prefixed_as_table_name: true,
/// #         pipeline_registry: None,
/// #         logging_client_name: None,
/// #         gateway_auth_client_name: None,
/// #         gateway_jdbc_allow_private_hosts: false,
/// #         gateway_jdbc_allowed_hosts: Vec::new(),
/// #         gateway_resilience_timeout_secs: 30,
/// #         gateway_resilience_read_max_retries: 1,
/// #         gateway_resilience_initial_backoff_ms: 100,
/// #         prometheus_metrics_enabled: false,
/// #         metrics_state: Arc::new(athena_rs::api::metrics::MetricsState::new()),
/// #     };
/// #     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()
        && let Ok(value) = redis.lock().await.get(cache_key).await
        && !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;
/// # use std::time::Instant;
/// # 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(),
/// #         process_start_time_seconds: 0,
/// #         process_started_at: Instant::now(),
/// #         pg_registry: Arc::new(PostgresClientRegistry::empty()),
/// #         jdbc_pool_cache,
/// #         gateway_force_camel_case_to_snake_case: false,
/// #         gateway_auto_cast_uuid_filter_values_to_text: true,
/// #         gateway_allow_schema_names_prefixed_as_table_name: true,
/// #         pipeline_registry: None,
/// #         logging_client_name: None,
/// #         gateway_auth_client_name: None,
/// #         gateway_jdbc_allow_private_hosts: false,
/// #         gateway_jdbc_allowed_hosts: Vec::new(),
/// #         gateway_resilience_timeout_secs: 30,
/// #         gateway_resilience_read_max_retries: 1,
/// #         gateway_resilience_initial_backoff_ms: 100,
/// #         prometheus_metrics_enabled: false,
/// #         metrics_state: Arc::new(athena_rs::api::metrics::MetricsState::new()),
/// #     };
/// #     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")
        && let Ok(cache_control_value) = cache_control_header.to_str()
        && 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")
        && let Ok(cache_control_value) = cache_control_header.to_str()
        && 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
            && 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()
        && let Ok(value) = redis.lock().await.get(cache_key).await
        && !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
            && arr.len() == 1
        {
            return Some(HttpResponse::Ok().json(&arr[0]));
        }
        return Some(HttpResponse::Ok().json(value));
    }
    None
}