liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! `liminal-server checkpoint-census` (DUR-024 R5): what one conversation's
//! checkpoint holds, counted and printed, never published; and without a
//! conversation (DUR-028 R6), what the whole store holds.
//!
//! The command opens the configured store (or a copy the operator names),
//! replays the conversation exactly as a rebuild would, prepares its
//! checkpoint capture exactly as a publication would, and prints one plain
//! line per figure: history nodes by kind with their bytes, the identities
//! captured, the operation log and outbox rows ahead of and behind the
//! latest locator, the body's bytes, the bound and the batches a publication
//! would commit. It writes nothing, prints no row body, and is reachable
//! from the command line only.

use std::path::Path;

use crate::error::ServerError;
use crate::server::participant::{checkpoint_census, store_census};

/// Counts one conversation's checkpoint, or the whole store when no
/// conversation is named, and answers the command's lines.
///
/// The store read is `store_path` when one is named and the configuration's
/// persistence path otherwise; the figures come back as the lines the
/// command prints.
///
/// # Errors
/// [`ServerError::ConfigValidation`] when the configuration names no
/// persistence path (and none was given) or no `[participant]` section, when
/// the store cannot be opened, or when the census refuses a step, named.
pub fn run_checkpoint_census(
    config_path: &Path,
    conversation_id: Option<u64>,
    store_path: Option<&Path>,
) -> Result<Vec<String>, ServerError> {
    let config = crate::config::file::load_config(config_path)?;
    let Some(participant) = config.participant.as_ref() else {
        return Err(ServerError::ConfigValidation {
            message: "the checkpoint census needs the [participant] section".to_owned(),
        });
    };
    let Some(persistence_path) = store_path.or(config.persistence_path.as_deref()) else {
        return Err(ServerError::ConfigValidation {
            message: "the checkpoint census needs persistence_path or --store; an ephemeral store has no checkpoint to count".to_owned(),
        });
    };
    let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
    let Some(conversation_id) = conversation_id else {
        return store_census(&store)
            .map(|census| census.lines())
            .map_err(|error| ServerError::ConfigValidation {
                message: format!("store census: {error}"),
            });
    };
    let census = checkpoint_census(&store, participant, conversation_id).map_err(|error| {
        ServerError::ConfigValidation {
            message: format!("checkpoint census of conversation {conversation_id}: {error}"),
        }
    })?;
    Ok(census.lines())
}

#[cfg(test)]
#[path = "checkpoint_census_tests.rs"]
mod tests;