live-stream 0.1.0

Consumer SDK for browsing and subscribing to live data feeds. Publishers use the live-feed crate.
Documentation
//! Browseable view over a parsed manifest.

use live_data::{FeedDescriptor, FeedManifest, FormatPreference, TransportTag};

/// Wraps a parsed [`FeedManifest`] and exposes filter, search, and
/// find-by-name helpers for consumer code that wants to browse what a
/// publisher offers.
#[derive(Debug, Clone)]
pub struct FeedCatalog {
    manifest: FeedManifest,
}

impl FeedCatalog {
    /// Take ownership of a manifest.
    pub fn new(manifest: FeedManifest) -> Self {
        Self { manifest }
    }

    /// Parse a manifest from JSON bytes.
    pub fn from_json(bytes: &[u8]) -> serde_json::Result<Self> {
        let manifest = serde_json::from_slice(bytes)?;
        Ok(Self::new(manifest))
    }

    /// Parse a manifest from a JSON string.
    pub fn from_json_str(s: &str) -> serde_json::Result<Self> {
        Self::from_json(s.as_bytes())
    }

    pub fn manifest(&self) -> &FeedManifest {
        &self.manifest
    }

    pub fn into_manifest(self) -> FeedManifest {
        self.manifest
    }

    pub fn protocol_version(&self) -> u32 {
        self.manifest.protocol_version
    }

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

    pub fn feeds(&self) -> &[FeedDescriptor] {
        &self.manifest.feeds
    }

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

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

    /// Locate a feed by exact name.
    pub fn find(&self, name: &str) -> Option<&FeedDescriptor> {
        self.manifest.feeds.iter().find(|f| f.name == name)
    }

    /// All feeds carrying the given tag.
    pub fn filter_by_tag<'a>(
        &'a self,
        tag: &'a str,
    ) -> impl Iterator<Item = &'a FeedDescriptor> + 'a {
        self.manifest
            .feeds
            .iter()
            .filter(move |f| f.tags.iter().any(|t| t == tag))
    }

    /// All feeds advertising the given transport.
    pub fn filter_by_transport(
        &self,
        t: TransportTag,
    ) -> impl Iterator<Item = &FeedDescriptor> {
        self.manifest.feeds.iter().filter(move |f| f.transports.contains(&t))
    }

    /// All feeds advertising the given format.
    pub fn filter_by_format(
        &self,
        f: FormatPreference,
    ) -> impl Iterator<Item = &FeedDescriptor> {
        self.manifest.feeds.iter().filter(move |feed| feed.formats.contains(&f))
    }

    /// Case-insensitive substring match on feed name or description.
    pub fn search<'a>(
        &'a self,
        query: &'a str,
    ) -> impl Iterator<Item = &'a FeedDescriptor> + 'a {
        let q = query.to_ascii_lowercase();
        self.manifest.feeds.iter().filter(move |f| {
            f.name.to_ascii_lowercase().contains(&q)
                || f.description
                    .as_ref()
                    .map(|d| d.to_ascii_lowercase().contains(&q))
                    .unwrap_or(false)
        })
    }
}

impl From<FeedManifest> for FeedCatalog {
    fn from(m: FeedManifest) -> Self {
        Self::new(m)
    }
}