liminal-server 0.21.1

Standalone server for the liminal messaging bus
Documentation
//! The console's readings (SRV-015 R1): numbers the map can be wired to, on
//! their own subject, `console.readings`.
//!
//! A source is a named kind of number about one kind of target (a device, a
//! station or a line), offered by the server or by a participant. A reading
//! is one source's number for one target. Every `console.readings` record is
//! the whole current set, so, as on `console.topology`, a reader's snapshot
//! is the subject's latest record; `console.topology` itself never carries a
//! reading (SRV-015 R1 and R4, amended 7 Oct 14:30).
//!
//! No source is required. A source nothing measures is absent, and an absent
//! reading means no reading, never zero (Tom 7 Oct 13:56: "we don't want to
//! sort of require like CPU and temperature").

use serde::{Serialize, Serializer};

use super::records::RECORD_VERSION;

/// The subject the readings records go out on.
pub const READINGS_SUBJECT: &str = "console.readings";

/// What a source's readings are about.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TargetKind {
    /// A device: this one or a peer.
    Device,
    /// A station.
    Station,
    /// A line.
    Line,
}

/// Who offers a source: the server, from what it measures itself, or the
/// participant that first published it.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Origin {
    /// The server's own measure, sent as `"server"`.
    Server,
    /// A participant's, sent as its id.
    Participant(String),
}

impl Serialize for Origin {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match self {
            Self::Server => serializer.serialize_str("server"),
            Self::Participant(id) => serializer.serialize_str(id),
        }
    }
}

/// A reading's number: always finite, with one zero, so two readings compare
/// equal exactly when they are the same number.
#[derive(Clone, Copy, Debug)]
pub struct ReadingValue(f64);

impl ReadingValue {
    /// The value, or `None` for NaN or an infinity. Minus zero is taken as
    /// zero.
    #[must_use]
    pub fn new(value: f64) -> Option<Self> {
        // adding zero turns minus zero into zero and leaves every other value as it was
        value.is_finite().then_some(Self(value + 0.0))
    }

    /// The number.
    #[must_use]
    pub const fn get(self) -> f64 {
        self.0
    }
}

impl PartialEq for ReadingValue {
    fn eq(&self, other: &Self) -> bool {
        self.0.to_bits() == other.0.to_bits()
    }
}

impl Eq for ReadingValue {}

impl Serialize for ReadingValue {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_f64(self.0)
    }
}

/// A named kind of number about one kind of target.
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Source {
    /// Dotted and lower case: `line.rate`, `worker.backlog`.
    pub id: String,
    /// How the console names it.
    pub label: String,
    /// Its unit: `msg/s`, `msg`, `ms`.
    pub unit: String,
    /// What its readings are about.
    pub target: TargetKind,
    /// Who offers it.
    pub from: Origin,
    /// Its range, low then high, when the source knows one.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub range: Option<[ReadingValue; 2]>,
}

/// One source's number for one target.
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Reading {
    /// The source's id.
    pub source: String,
    /// The id of the device, station or line it is about.
    pub target: String,
    /// The number.
    pub value: ReadingValue,
    /// When it was read, in wall-clock milliseconds since the epoch.
    pub at: u64,
}

/// One `console.readings` record: every source and reading as they stand.
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct ReadingsRecord {
    v: u64,
    #[serde(rename = "type")]
    record_type: &'static str,
    /// One more than the previous readings record's, from 1 at start.
    pub rev: u64,
    /// The `console.topology` document's rev the targets were read against.
    pub topology_rev: u64,
    /// Every source offered now.
    pub sources: Vec<Source>,
    /// Every reading held now.
    pub readings: Vec<Reading>,
}

impl ReadingsRecord {
    /// A record at `rev`, read against topology document `topology_rev`.
    #[must_use]
    pub const fn new(
        rev: u64,
        topology_rev: u64,
        sources: Vec<Source>,
        readings: Vec<Reading>,
    ) -> Self {
        Self {
            v: RECORD_VERSION,
            record_type: "readings",
            rev,
            topology_rev,
            sources,
            readings,
        }
    }
}