reliar-core 0.4.1

Pure envelope/message model shared by every Reliar crate — no storage or transport dependency.
Documentation
//! Stable message contract identity (ADR 0010).

use core::fmt;
use std::borrow::Cow;

/// A type that can be built into an [`Envelope`](crate::Envelope) and persisted or published.
///
/// `TYPE`/`VERSION` are stable **application contracts**: they identify a message across
/// serialization, storage and the wire, and are never derived from
/// `std::any::type_name::<T>()` or a module path — renaming or moving the Rust type must not
/// orphan a pending row or a message already in flight (ADR 0010).
///
/// ```
/// use reliar_core::Message;
///
/// #[derive(serde::Serialize, serde::Deserialize)]
/// struct OrderCancelled {
///     order_id: u64,
/// }
///
/// impl Message for OrderCancelled {
///     const TYPE: &'static str = "orders.cancelled";
///     const VERSION: u16 = 1;
/// }
///
/// assert_eq!(OrderCancelled::TYPE, "orders.cancelled");
/// assert_eq!(OrderCancelled::VERSION, 1);
/// ```
pub trait Message: serde::Serialize + serde::de::DeserializeOwned {
    /// The message's name, e.g. `"orders.created"`. Stable once anything has published it.
    const TYPE: &'static str;
    /// The message's version. Bump when the wire shape changes incompatibly.
    const VERSION: u16;
}

/// A message's name and version, carried separately so a query can filter a name across every
/// version. Renders as `"{name}.v{version}"` via its [`Display`](fmt::Display) impl.
///
/// ```
/// use reliar_core::{Message, MessageType};
///
/// #[derive(serde::Serialize, serde::Deserialize)]
/// struct OrderCreated;
/// impl Message for OrderCreated {
///     const TYPE: &'static str = "orders.created";
///     const VERSION: u16 = 1;
/// }
///
/// let message_type = MessageType::of::<OrderCreated>();
/// assert_eq!(message_type.name(), "orders.created");
/// assert_eq!(message_type.version(), 1);
/// assert_eq!(message_type.to_string(), "orders.created.v1");
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MessageType {
    name: Cow<'static, str>,

    version: u16,
}

impl MessageType {
    /// Builds a `MessageType` from a `'static` name and a version.
    ///
    /// ```
    /// use reliar_core::MessageType;
    ///
    /// let message_type = MessageType::new("orders.created", 1);
    /// assert_eq!(message_type.to_string(), "orders.created.v1");
    /// ```
    #[must_use]
    pub const fn new(name: &'static str, version: u16) -> Self {
        Self {
            name: Cow::Borrowed(name),
            version,
        }
    }

    /// Rehydration path: a provider reads `message_type`/`message_version` columns back into a
    /// `MessageType` for which it has no Rust type.
    ///
    /// ```
    /// use reliar_core::MessageType;
    ///
    /// let message_type = MessageType::from_parts("orders.created".to_string(), 1);
    /// assert_eq!(message_type.name(), "orders.created");
    /// ```
    pub fn from_parts(name: impl Into<Cow<'static, str>>, version: u16) -> Self {
        Self {
            name: name.into(),
            version,
        }
    }

    /// Builds the `MessageType` a `T: Message` declares: `T::TYPE` + `T::VERSION`. Never derived
    /// from `std::any::type_name::<T>()`.
    ///
    /// ```
    /// use reliar_core::{Message, MessageType};
    ///
    /// #[derive(serde::Serialize, serde::Deserialize)]
    /// struct Ping;
    /// impl Message for Ping {
    ///     const TYPE: &'static str = "ping";
    ///     const VERSION: u16 = 1;
    /// }
    ///
    /// assert_eq!(MessageType::of::<Ping>(), MessageType::new("ping", 1));
    /// ```
    #[must_use]
    pub fn of<T: Message>() -> Self {
        Self::new(T::TYPE, T::VERSION)
    }

    /// The message name, e.g. `"orders.created"`.
    ///
    /// ```
    /// use reliar_core::MessageType;
    ///
    /// assert_eq!(MessageType::new("orders.created", 1).name(), "orders.created");
    /// ```
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// The message version.
    ///
    /// ```
    /// use reliar_core::MessageType;
    ///
    /// assert_eq!(MessageType::new("orders.created", 3).version(), 3);
    /// ```
    #[must_use]
    pub const fn version(&self) -> u16 {
        self.version
    }
}

/// Renders `"{name}.v{version}"`, e.g. `orders.created.v1`. **A stable public contract**:
/// clients parse this string. Two distinct Rust types sharing `TYPE`/`VERSION` render
/// identically — that is intended, not a bug (ADR 0010).
impl fmt::Display for MessageType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}.v{}", self.name, self.version)
    }
}