intrepid-core 0.3.0

Manage complex async business logic with ease
Documentation
mod frame_outbox;

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

pub use frame_outbox::FrameOutbox;

/// An addressed frame.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MessageFrame {
    /// The address of the frame.
    pub uri: String,
    /// The frame contents.
    pub data: Bytes,
    /// The frame metadata.
    pub meta: Bytes,
}

/// A frame that can be processed by an action.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum Frame {
    /// A unit frame, with nothing in it.
    #[default]
    Unit,
    /// An error frame.
    Error(Bytes),
    /// An unaddressed chunk of bytes.
    Anonymous(Bytes),
    /// An addressed frame.
    Message(MessageFrame),
}

impl Frame {
    /// Create a new frame from a given set of bytes.
    pub fn new(bytes: impl Into<Bytes>) -> Self {
        let bytes = bytes.into();

        if bytes.is_empty() {
            Frame::Unit
        } else {
            Frame::Anonymous(bytes)
        }
    }

    /// Create a message frame.
    pub fn message(uri: impl AsRef<str>, data: impl Into<Frame>, meta: impl Into<Frame>) -> Self {
        Frame::Message(MessageFrame {
            uri: uri.as_ref().into(),
            data: data.into().into_bytes(),
            meta: meta.into().into_bytes(),
        })
    }

    /// The length of the frame in bytes.
    ///
    pub fn len(&self) -> usize {
        match self {
            Frame::Unit => 0,
            Frame::Error(data)
            | Frame::Anonymous(data)
            | Frame::Message(MessageFrame { data, .. }) => data.len(),
        }
    }

    /// Check if the frame is empty.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Get a reference to the frame as a slice of bytes.
    pub fn as_bytes(&self) -> &[u8] {
        match self {
            Frame::Unit => &[],
            Frame::Error(data)
            | Frame::Anonymous(data)
            | Frame::Message(MessageFrame { data, .. }) => data.as_ref(),
        }
    }

    /// Convert the frame's body into a vector of bytes.
    ///
    pub fn into_bytes(self) -> Bytes {
        match self {
            Frame::Unit => Bytes::new(),
            Frame::Error(data)
            | Frame::Anonymous(data)
            | Frame::Message(MessageFrame { data, .. }) => data,
        }
    }
}

impl AsRef<[u8]> for Frame {
    fn as_ref(&self) -> &[u8] {
        self.as_bytes()
    }
}

impl From<Vec<u8>> for Frame {
    fn from(value: Vec<u8>) -> Self {
        Bytes::from(value).into()
    }
}

impl<T> From<Vec<T>> for Frame
where
    T: Into<Frame>,
{
    fn from(value: Vec<T>) -> Self {
        let mut bytes: Vec<u8> = vec![];

        for value in value {
            bytes.extend_from_slice(value.into().as_ref());
        }

        Bytes::from(bytes).into()
    }
}

impl From<()> for Frame {
    fn from(_: ()) -> Self {
        Frame::default()
    }
}

impl From<Bytes> for Frame {
    fn from(bytes: Bytes) -> Self {
        Frame::new(bytes)
    }
}

impl From<Frame> for Bytes {
    fn from(frame: Frame) -> Bytes {
        frame.into_bytes()
    }
}

impl From<Frame> for String {
    fn from(value: Frame) -> Self {
        String::from_utf8_lossy(value.as_ref()).into_owned()
    }
}

impl From<String> for Frame {
    fn from(value: String) -> Self {
        Frame::new(value)
    }
}

impl<T> From<Option<T>> for Frame
where
    T: Into<Frame>,
{
    fn from(option: Option<T>) -> Self {
        match option {
            Some(value) => value.into(),
            None => Frame::default(),
        }
    }
}

impl<T, E> From<Result<T, E>> for Frame
where
    T: Into<Frame>,
    E: Into<Frame>,
{
    fn from(result: Result<T, E>) -> Self {
        match result {
            Ok(value) => value.into(),
            Err(error) => error.into(),
        }
    }
}

impl From<BoxError> for Frame {
    fn from(error: BoxError) -> Self {
        error.to_string().into()
    }
}