rifts 0.3.10

Rift Realtime Protocol / 1.0 — server + client implementation
Documentation
//! # Stream Segment — Continuously Ordered Data Streams (spec section 8)
//!
//! A stream is an ordered sequence of segments (`StreamSegment`) sharing the same `stream_id`.
//! Segments are strictly ordered (guaranteed by `seq`) until a terminal segment
//! with `final_segment = true` is received.
//!
//! ## Typical Use Cases
//!
//! - AI model token streaming output
//! - Large file chunked transfer
//! - Real-time audio/video frame transmission
//! - Long-running task progress updates
//!
//! ## Stream Lifecycle
//!
//! ```text
//! Seg 0 ──▶ Seg 1 ──▶ Seg 2 ──▶ ... ──▶ Seg N (final)
//!   │         │         │                   │
//!   seq=0    seq=1    seq=2              seq=N
//!                                  final_segment=true
//! ```

use bytes::Bytes;
use serde::{Deserialize, Serialize};

use crate::error::Result;

/// A segment within a continuously ordered data stream.
///
/// Typical use cases include AI token streams, file transfers, audio/video frames, etc.
/// All messages belonging to the same stream share a `stream_id` and are ordered by `seq`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamSegment {
    /// Stream identifier — all segments of the same stream share this value.
    ///
    /// Generated by the sender when the stream starts (typically a ULID).
    pub stream_id: String,

    /// Monotonically increasing sequence number within the stream (starting from 0).
    ///
    /// The receiver uses this to order segments and detect any missing ones.
    pub seq: u64,

    /// Whether this is the final segment.
    ///
    /// `true` indicates the stream has ended; the receiver should complete stream processing
    /// and clean up associated state.
    pub final_segment: bool,

    /// Schema identifier, formatted as `{domain}.{name}@{major}.{minor}`.
    ///
    /// Defines the structure of `payload`, e.g. `"ai.token@1.0"`, `"file.chunk@1.0"`.
    pub schema: String,

    /// Segment payload in JSON format.
    ///
    /// For AI token streams, this is typically `{"text": "..."}`.
    /// For file transfers, this is typically a Base64-encoded data chunk.
    pub payload: serde_json::Value,
}

/// Serializes a StreamSegment to JSON bytes.
pub fn encode_stream(s: &StreamSegment) -> Result<Bytes> {
    Ok(Bytes::from(serde_json::to_vec(s)?))
}

/// Deserializes a StreamSegment from JSON bytes.
pub fn decode_stream(bytes: &[u8]) -> Result<StreamSegment> {
    Ok(serde_json::from_slice(bytes)?)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn round_trip() {
        let s = StreamSegment {
            stream_id: "s1".into(),
            seq: 3,
            final_segment: false,
            schema: "ai.token@1.0".into(),
            payload: serde_json::json!({"text": "hello"}),
        };
        let bytes = encode_stream(&s).unwrap();
        let back = decode_stream(&bytes).unwrap();
        assert_eq!(back.seq, 3);
        assert!(!back.final_segment);
    }
}