Skip to main content

liminal_server/console/
topology.rs

1//! The topology document (SRV-011 R1), built from in-memory state only.
2
3use liminal::channel::ChannelMode;
4
5use crate::ServerError;
6use crate::server::connection::channel_registry::{ChannelOrigin, ChannelStatus};
7use crate::server::connection::services::LiminalConnectionServices;
8
9/// One channel (a line) as the console draws it.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct ChannelNode {
12    /// The channel name.
13    pub name: String,
14    /// `durable` or `ephemeral`.
15    pub mode: &'static str,
16    /// `live` (admitting) or `held` (quiesced).
17    pub state: &'static str,
18    /// `config` (boot) or `runtime` (registered live).
19    pub origin: &'static str,
20    /// The quiesce cause, when held.
21    pub reason: Option<String>,
22}
23
24/// The topology document.
25#[derive(Clone, Debug, Default, PartialEq, Eq)]
26pub struct TopologySnapshot {
27    /// Every channel on the roster, sorted by name.
28    pub channels: Vec<ChannelNode>,
29    /// Every registered conversation responder subject, sorted.
30    pub services: Vec<String>,
31}
32
33/// Builds the topology from the roster, read once (pinned by
34/// `the_snapshot_reads_the_roster_once`), and the responder registry. Reads
35/// no store row.
36///
37/// # Errors
38/// Returns [`ServerError`] when the roster or responder lock is poisoned.
39pub fn snapshot(services: &LiminalConnectionServices) -> Result<TopologySnapshot, ServerError> {
40    let roster_error = |error: &dyn std::fmt::Display| ServerError::ListenerAccept {
41        message: format!("console topology: {error}"),
42    };
43    let census = services
44        .channel_census()
45        .map_err(|error| roster_error(&error))?;
46    let mut channels = Vec::with_capacity(census.len());
47    for (name, status) in census {
48        let node = match status {
49            ChannelStatus::NotRegistered => continue,
50            ChannelStatus::Active { origin, mode, .. } => ChannelNode {
51                name,
52                mode: mode_word(mode),
53                state: "live",
54                origin: origin_word(origin),
55                reason: None,
56            },
57            ChannelStatus::Quiesced {
58                reason,
59                origin,
60                mode,
61            } => ChannelNode {
62                name,
63                mode: mode_word(mode),
64                state: "held",
65                origin: origin_word(origin),
66                reason: Some(reason),
67            },
68        };
69        channels.push(node);
70    }
71    Ok(TopologySnapshot {
72        channels,
73        services: services.responder_subjects()?,
74    })
75}
76
77const fn mode_word(mode: ChannelMode) -> &'static str {
78    match mode {
79        ChannelMode::Durable => "durable",
80        ChannelMode::Ephemeral => "ephemeral",
81    }
82}
83
84const fn origin_word(origin: ChannelOrigin) -> &'static str {
85    match origin {
86        ChannelOrigin::BootConfigured => "config",
87        ChannelOrigin::RuntimeRegistered => "runtime",
88    }
89}