//! Core data model: the framed, channel-multiplexed stream (spec §4). //! //! A stream is identified by the producing node and lifetime. Frames carry a //! stream-local numeric channel id plus the mux-assigned position and opaque //! payload bytes. Channel names, payload content kinds, and display metadata live //! in catalog descriptors that consumers receive before or alongside frames. use std::fmt; use std::sync::Arc; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// A position assigned by a node's mux (spec §5.2). /// /// Positions are **monotonic** and **gap-free** within a single node's stream: /// the mux never reuses one and never skips one in its numbering. A position /// that is assigned but never delivered surfaces downstream as a missing /// position — a detectable gap (spec §5.3, §7.5). #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] pub struct Position(pub u64); impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Stream-local numeric channel id. /// /// `ChannelId(0)` is reserved for the datastream frame-timing sidecar. Every /// other id is allocated by the stream owner and is meaningful only with the /// corresponding [`StreamId`]. Consumers resolve frames by `(stream, channel)`. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct ChannelId(pub u32); impl fmt::Display for ChannelId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Payload content kind without schema details. Used for subscription filters. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ChannelContentKind { Bytes, TextStream, JsonRecord, } /// How consumers should decode/display payload bytes for a channel. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ChannelContent { Bytes, TextStream, JsonRecord { schema: Option }, } impl ChannelContent { pub fn kind(&self) -> ChannelContentKind { match self { ChannelContent::Bytes => ChannelContentKind::Bytes, ChannelContent::TextStream => ChannelContentKind::TextStream, ChannelContent::JsonRecord { .. } => ChannelContentKind::JsonRecord, } } } /// Where a stream originates from in the current process topology. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum StreamOrigin { Orchestrator, Bootstrap, RemoteNode, } /// The stable identity of a node that produces a stream (spec §4.4, §8.1). #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct NodeId(Arc); impl NodeId { /// Construct a node id from any string-like value. pub fn new(id: impl AsRef) -> Self { NodeId(Arc::from(id.as_ref())) } /// The id as a string slice. pub fn as_str(&self) -> &str { &self.0 } } impl From<&str> for NodeId { fn from(s: &str) -> Self { NodeId::new(s) } } impl From for NodeId { fn from(s: String) -> Self { NodeId::new(s) } } impl fmt::Display for NodeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl fmt::Debug for NodeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NodeId({:?})", &self.0) } } impl Serialize for NodeId { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(self.as_str()) } } impl<'de> Deserialize<'de> for NodeId { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { String::deserialize(deserializer).map(NodeId::new) } } /// A lifetime discriminator distinguishing a node's incarnations (spec §8.4). #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct Lifetime(pub u64); /// Identifies exactly one stored stream: a node plus the life it was produced in. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct StreamId { /// Which node produced the stream. pub node: NodeId, /// Which life of that node. pub life: Lifetime, } impl StreamId { /// Construct a stream id from a node and a lifetime. pub fn new(node: impl Into, life: Lifetime) -> Self { StreamId { node: node.into(), life, } } } impl fmt::Display for StreamId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}#{}", self.node, self.life.0) } } /// Stream metadata declared by the stream owner. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct StreamDescriptor { pub stream: StreamId, pub label: Option, pub origin: StreamOrigin, } /// Channel metadata declared by the stream owner. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ChannelDescriptor { pub stream: StreamId, pub id: ChannelId, pub name: String, pub label: Option, pub content: ChannelContent, } /// Globally resolved channel identity: a stream plus that stream's numeric lane. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct ChannelRef { pub stream: StreamId, pub channel: ChannelId, } /// A delivered frame with its stream-local channel resolved to a [`ChannelRef`]. #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct FrameDelivery { pub channel: ChannelRef, pub position: Position, pub payload: Vec, } /// Catalog and frame events delivered to subscribers. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum DatastreamEvent { StreamDeclared(StreamDescriptor), ChannelDeclared(ChannelDescriptor), Frame(FrameDelivery), StreamEnded(StreamId), } /// Source filter used by subscribers. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SourceFilter { All, Origin(StreamOrigin), Node(NodeId), Stream(StreamId), } /// Channel filter used by subscribers. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ChannelFilter { All, Name(String), Prefix(String), Content(ChannelContentKind), } /// A subscription request for catalog metadata and future frame events. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SubscriptionRequest { pub sources: SourceFilter, pub channels: ChannelFilter, } impl SubscriptionRequest { pub fn all() -> Self { Self { sources: SourceFilter::All, channels: ChannelFilter::All, } } } /// The unit the mux emits (spec §4.1): bytes tagged with a channel and a position. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Frame { /// The stream-local lane these bytes belong to. pub channel: ChannelId, /// The mux-assigned position within the node's stream. pub position: Position, /// The opaque payload bytes. pub payload: Vec, } impl Frame { /// Assemble a frame from its parts. pub fn new(channel: ChannelId, position: Position, payload: Vec) -> Self { Frame { channel, position, payload, } } } impl fmt::Debug for Frame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut dbg = f.debug_struct("Frame"); dbg.field("channel", &self.channel) .field("position", &self.position); match std::str::from_utf8(&self.payload) { Ok(text) => dbg.field("payload", &text), Err(_) => dbg.field("payload", &format_args!("<{} bytes>", self.payload.len())), }; dbg.finish() } }