csaf-crud 0.3.2

CSAF 2.0 / 2.1 advisory CRUD server with HATEOAS JSON API and HTML UI (TLS 1.3, HTTP/1.1 + HTTP/2 + HTTP/3)
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Pierre Gronau, ndaal in Cologne

//! Shared application state passed to all request handlers.

use std::sync::{Arc, RwLock};

use csaf_core::config::AppConfig;
use csaf_core::storage::CsafStorage;
use csaf_models::db::DbPool;
use csaf_models::settings::Settings;

/// Central application state shared across all request handlers.
#[derive(Clone)]
pub struct AppState {
    inner: Arc<AppStateInner>,
}

struct AppStateInner {
    csaf_storage: CsafStorage,
    db_pool: DbPool,
    config: AppConfig,
    settings: RwLock<Settings>,
}

impl AppState {
    /// Create a new application state.
    #[must_use]
    pub fn new(
        csaf_storage: CsafStorage,
        db_pool: DbPool,
        config: AppConfig,
        settings: Settings,
    ) -> Self {
        Self {
            inner: Arc::new(AppStateInner {
                csaf_storage,
                db_pool,
                config,
                settings: RwLock::new(settings),
            }),
        }
    }

    /// Access the embedded CSAF storage (redb).
    #[must_use]
    pub fn csaf_storage(&self) -> &CsafStorage {
        &self.inner.csaf_storage
    }

    /// Access the SQLite connection pool.
    #[must_use]
    pub fn db_pool(&self) -> &DbPool {
        &self.inner.db_pool
    }

    /// Access the application configuration.
    #[must_use]
    pub fn config(&self) -> &AppConfig {
        &self.inner.config
    }

    /// Read a clone of the current settings.
    ///
    /// Recovers from poisoned locks by returning the last-known value.
    #[must_use]
    pub fn settings(&self) -> Settings {
        let guard = self
            .inner
            .settings
            .read()
            .unwrap_or_else(|p| p.into_inner());
        let cloned = guard.clone();
        drop(guard);
        cloned
    }

    /// Update settings in memory and persist to storage.
    ///
    /// # Errors
    ///
    /// Returns an error if persistence fails.
    pub fn update_settings(&self, new_settings: Settings) -> csaf_core::error::Result<()> {
        self.inner.csaf_storage.put_settings(&new_settings)?;
        let mut guard = self
            .inner
            .settings
            .write()
            .unwrap_or_else(|p| p.into_inner());
        *guard = new_settings;
        drop(guard);
        Ok(())
    }
}