live-feed 0.1.0

Publisher SDK for advertising and serving live data feeds. Consumers use the live-stream crate.
Documentation
//! Live registry of declared feeds with manifest snapshots.

use std::collections::HashMap;

use live_data::{FeedDescriptor, FeedManifest};

use crate::validate::{ValidationError, validate_descriptor};

/// Mutable registry of feed descriptors. Produces a [`FeedManifest`]
/// snapshot on demand and round-trips through JSON for HTTP-style
/// manifest exchange.
///
/// Feed order is preserved across snapshots so consumers see a stable
/// listing regardless of internal map ordering.
#[derive(Debug, Clone)]
pub struct ManifestRegistry {
    server_version: String,
    feeds: HashMap<String, FeedDescriptor>,
    order: Vec<String>,
}

impl ManifestRegistry {
    pub fn new(server_version: impl Into<String>) -> Self {
        Self {
            server_version: server_version.into(),
            feeds: HashMap::new(),
            order: Vec::new(),
        }
    }

    pub fn server_version(&self) -> &str {
        &self.server_version
    }

    /// Register a descriptor. Errors if the descriptor fails
    /// validation or a feed of the same name is already registered.
    pub fn register(&mut self, d: FeedDescriptor) -> Result<(), ValidationError> {
        validate_descriptor(&d)?;
        if self.feeds.contains_key(&d.name) {
            return Err(ValidationError::DuplicateFeedName(d.name));
        }
        self.order.push(d.name.clone());
        self.feeds.insert(d.name.clone(), d);
        Ok(())
    }

    pub fn unregister(&mut self, name: &str) -> Option<FeedDescriptor> {
        let descriptor = self.feeds.remove(name)?;
        self.order.retain(|n| n != name);
        Some(descriptor)
    }

    pub fn get(&self, name: &str) -> Option<&FeedDescriptor> {
        self.feeds.get(name)
    }

    pub fn feed_names(&self) -> impl Iterator<Item = &str> {
        self.order.iter().map(String::as_str)
    }

    pub fn len(&self) -> usize {
        self.order.len()
    }

    pub fn is_empty(&self) -> bool {
        self.order.is_empty()
    }

    /// Take a snapshot of the registry as a [`FeedManifest`].
    pub fn manifest(&self) -> FeedManifest {
        let feeds = self
            .order
            .iter()
            .filter_map(|n| self.feeds.get(n).cloned())
            .collect();
        FeedManifest::new(self.server_version.clone(), feeds)
    }

    /// Snapshot the registry and serialise to compact JSON.
    pub fn manifest_json(&self) -> serde_json::Result<String> {
        serde_json::to_string(&self.manifest())
    }

    /// Snapshot the registry and serialise to pretty JSON.
    pub fn manifest_json_pretty(&self) -> serde_json::Result<String> {
        serde_json::to_string_pretty(&self.manifest())
    }
}