athena_rs 2.9.1

Database gateway API
Documentation
//! API Registry endpoints.
//!
//! Lists available APIs and fetches registry entries by id, with simple caching.

use actix_web::{HttpRequest, Responder, get, web::Data};
use serde_json::Value;
// use tracing::info;

// crate imports
use crate::AppState;
use crate::api::client_context::logging_pool;
use crate::api::response::{api_ok, internal_error};
use crate::data::api_registry::{get_api_registry_by_id, list_api_registry_entries};

#[get("/registry")]
/// Returns all entries from the API registry table with lightweight caching.
///
/// The handler stores the list in `AppState` cache under `api_registry_list` and
/// honors `Cache-Control: no-cache` when callers explicitly want a fresh listing.
/// On cache miss it calls into the athena_logging database via `list_api_registry_entries`
/// and returns the raw `Vec<Value>` payload in a `200 OK`.
async fn api_registry(req: HttpRequest, app_state: Data<AppState>) -> impl Responder {
    let cache_key = "api_registry_list".to_string();

    // Check for cache-control no-cache header
    let should_bypass_cache = req
        .headers()
        .get("cache-control")
        .and_then(|h| h.to_str().ok())
        .map(|s| s.contains("no-cache"))
        .unwrap_or(false);

    if should_bypass_cache {
        app_state.cache.invalidate(&cache_key).await;
    }

    // Try to get from cache first
    if let Some(cached_entries) = app_state.cache.get(&cache_key).await {
        return api_ok(cached_entries);
    }

    let pool = match logging_pool(app_state.get_ref()) {
        Ok(p) => p,
        Err(resp) => return resp,
    };

    // If not in cache, fetch from database
    match list_api_registry_entries(&pool).await {
        Ok(entries) => {
            let entries_value = serde_json::to_value(&entries).unwrap_or(Value::Null);
            app_state
                .cache
                .insert(cache_key, entries_value.clone())
                .await;
            api_ok(entries)
        }
        Err(err) => internal_error("Failed to list API registry entries", err),
    }
}

#[get("/registry/{api_registry_id}")]
/// Fetches a single API registry entry by `api_registry_id`, again with caching.
///
/// The `api_registry_id` path parameter is used as the cache key suffix. The handler
/// supports `Cache-Control: no-cache` to invalidate the cached entry and returns
/// HTTP `200` with the row on success or `500` when the lookup fails.
async fn api_registry_by_id(
    path: actix_web::web::Path<String>,
    req: HttpRequest,
    app_state: Data<AppState>,
) -> impl Responder {
    let api_registry_id = path.into_inner();
    let cache_key = format!("api_registry_{}", api_registry_id);

    // Check for cache-control no-cache header
    let should_bypass_cache = req
        .headers()
        .get("cache-control")
        .and_then(|h| h.to_str().ok())
        .map(|s| s.contains("no-cache"))
        .unwrap_or(false);

    if should_bypass_cache {
        app_state.cache.invalidate(&cache_key).await;
    }

    // Try to get from cache first
    if let Some(cached_entry) = app_state.cache.get(&cache_key).await {
        return api_ok(cached_entry);
    }

    let pool = match logging_pool(app_state.get_ref()) {
        Ok(p) => p,
        Err(resp) => return resp,
    };

    // If not in cache, fetch from database
    match get_api_registry_by_id(&pool, &api_registry_id).await {
        Ok(entry) => {
            let entry_value = serde_json::to_value(&entry).unwrap_or(Value::Null);
            app_state.cache.insert(cache_key, entry_value.clone()).await;
            api_ok(entry)
        }
        Err(err) => internal_error("Failed to get API registry entry", err),
    }
}