csaf-core 1.4.8

CSAF storage, validation, sidecar generation, import/export
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Pierre Gronau, ndaal in Cologne

//! Compile-time invariants for `csaf-core`, per `skills/rust-static-assertions`.
//!
//! Every assertion here fails the **build**, not a test run. That is the whole
//! point: these are properties a runtime test cannot catch reliably, because
//! by the time you notice them at runtime the damage is a deadlock, a data
//! race, or a type that silently stopped crossing an `.await`.
//!
//! Concretely, each block below guards against a specific regression that a
//! well-meaning refactor could introduce without any test going red:
//!
//! - A handle shared across the tokio runtime silently losing `Send`/`Sync`
//!   because someone swapped an `Arc<Mutex<_>>` for an `Rc<RefCell<_>>`.
//! - An error type ceasing to be `Send + Sync + 'static`, so it can no longer
//!   be boxed into `anyhow::Error` or returned from a spawned task — which
//!   surfaces as an unrelated, confusing compile error somewhere else.
//! - A trait losing object-safety, breaking `Arc<dyn Trait>` injection (the
//!   transport seam the whole offline updater suite depends on).
//! - An enum growing an unexpectedly large variant, quietly inflating every
//!   `Result<_, E>` in the crate.

use static_assertions::{assert_fields, assert_impl_all, assert_obj_safe};

// ---------------------------------------------------------------------------
// 1. Handles shared across the async runtime must stay Send + Sync
// ---------------------------------------------------------------------------

// `CsafStorage` is opened once in `main` and read from concurrent request
// handlers. If it stops being `Sync`, every handler that touches it stops
// compiling — but only after someone has already restructured the storage
// layer. Fail here instead, at the point of the change.
assert_impl_all!(csaf_core::storage::CsafStorage: Send, Sync);

// `AppConfig` is cloned into background tasks and read from handlers.
assert_impl_all!(csaf_core::config::AppConfig: Send, Sync, Clone);

// The SQLite pool is handed to blocking tasks via `spawn_blocking`, which
// requires `Send + 'static`.
assert_impl_all!(csaf_models::db::DbPool: Send, Sync);

// ---------------------------------------------------------------------------
// 2. The error-type contract
// ---------------------------------------------------------------------------

// Errors cross `.await` points and task boundaries and get boxed into
// `anyhow::Error`, which requires `Error + Send + Sync + 'static`. Losing any
// one of these is the classic "why does this no longer compile three modules
// away" regression.
assert_impl_all!(
    csaf_core::error::CsafError: std::error::Error,
    core::fmt::Debug,
    core::fmt::Display,
    Send,
    Sync,
);

// The updater's error is returned from `perform` and rendered by both
// binaries' CLI dispatch, so it carries the same contract.
assert_impl_all!(
    csaf_core::updater::UpdateError: std::error::Error,
    core::fmt::Debug,
    core::fmt::Display,
    Send,
    Sync,
);

// ---------------------------------------------------------------------------
// 3. Object-safety of the injected transport
// ---------------------------------------------------------------------------

// The entire offline self-update test suite injects `Arc<dyn HttpClient>`.
// If this trait ever stops being object-safe (a generic method, a `Self`
// return), the seam disappears and the tests silently fall back to hitting
// the real network — or stop compiling in a way that invites "just delete
// the test". Pin it.
assert_obj_safe!(self_update::http_client::HttpClient);
assert_obj_safe!(self_update::http_client::HttpResponse);

// ---------------------------------------------------------------------------
// 4. Update outcomes stay cheap, comparable and inspectable
// ---------------------------------------------------------------------------

// `UpdateOutcome` is compared in tests and matched in both binaries.
assert_impl_all!(
    csaf_core::updater::UpdateOutcome: core::fmt::Debug,
    Clone,
    PartialEq,
    Eq,
    Send,
    Sync,
);

// `UpdateFlags` is passed by value through the CLI dispatch of both binaries;
// `Copy` is what keeps that free and prevents an accidental clone-per-flag.
assert_impl_all!(
    csaf_core::update_cli::UpdateFlags: Copy,
    Clone,
    Default,
    core::fmt::Debug,
    PartialEq,
    Eq,
);

// The three flag fields ARE the CLI contract. Renaming one silently breaks
// the shared dispatch for whichever binary was not updated in the same
// commit, so pin the names at compile time.
assert_fields!(csaf_core::update_cli::UpdateFlags: check_update, self_update, no_self_update);

#[test]
fn compile_time_invariants_hold() {
    // The assertions above run at compile time; this test exists so the file
    // is a visible, named gate in `cargo test` output rather than an empty
    // target that looks skipped.
}