intercom-rs 1.1.1

A fully typed async wrapper for NATS with JetStream support
Documentation
//! Message encoding and decoding with pluggable codec support.
//!
//! This module provides two codec implementations:
//! - `MsgPackCodec` - MessagePack serialization (default, via rmp-serde)
//! - `JsonCodec` - JSON serialization (via serde_json)
//!
//! Enable the desired codec via cargo features:
//! - `msgpack` (default) - MessagePack codec
//! - `json` - JSON codec

use serde::{de::DeserializeOwned, Serialize};

use crate::error::Result;

/// Marker trait for codec types.
///
/// This trait is implemented by codec marker types like [`MsgPackCodec`] and [`JsonCodec`].
/// It provides the actual encode/decode functionality.
pub trait CodecType: Clone + Send + Sync + 'static {
    /// Encode a value to bytes.
    fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>>;

    /// Decode a value from bytes.
    fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T>;
}

/// MessagePack codec marker type.
///
/// Uses rmp-serde for efficient binary serialization.
///
/// # Example
///
/// ```no_run
/// use intercom::{Client, MsgPackCodec};
///
/// # async fn example() -> intercom::Result<()> {
/// let client = Client::<MsgPackCodec>::connect("nats://localhost:4222").await?;
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "msgpack")]
#[derive(Debug, Clone, Copy, Default)]
pub struct MsgPackCodec;

#[cfg(feature = "msgpack")]
impl CodecType for MsgPackCodec {
    fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
        Ok(rmp_serde::to_vec(value)?)
    }

    fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T> {
        Ok(rmp_serde::from_slice(data)?)
    }
}

/// JSON codec marker type.
///
/// Uses serde_json for human-readable serialization.
///
/// # Example
///
/// ```no_run
/// use intercom::{Client, JsonCodec};
///
/// # async fn example() -> intercom::Result<()> {
/// let client = Client::<JsonCodec>::connect("nats://localhost:4222").await?;
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "json")]
#[derive(Debug, Clone, Copy, Default)]
pub struct JsonCodec;

#[cfg(feature = "json")]
impl CodecType for JsonCodec {
    fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
        Ok(serde_json::to_vec(value)?)
    }

    fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T> {
        Ok(serde_json::from_slice(data)?)
    }
}

/// Trait for encoding messages.
pub trait Encode {
    /// Encode a message to bytes.
    fn encode(&self) -> Result<Vec<u8>>;
}

/// Trait for decoding messages.
pub trait Decode: Sized {
    /// Decode a message from bytes.
    fn decode(data: &[u8]) -> Result<Self>;
}

/// Trait for encoding and decoding messages.
pub trait Codec: Encode + Decode {}

// Default codec type alias for convenience
#[cfg(feature = "msgpack")]
/// The default codec type (MessagePack when msgpack feature is enabled).
pub type DefaultCodec = MsgPackCodec;

#[cfg(all(feature = "json", not(feature = "msgpack")))]
/// The default codec type (JSON when only json feature is enabled).
pub type DefaultCodec = JsonCodec;

#[cfg(test)]
mod tests {
    use super::*;
    use serde::{Deserialize, Serialize};

    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
    struct TestMessage {
        id: u64,
        content: String,
    }

    #[cfg(feature = "msgpack")]
    #[test]
    fn test_msgpack_encode_decode_roundtrip() {
        let msg = TestMessage {
            id: 42,
            content: "hello world".to_string(),
        };

        let encoded = MsgPackCodec::encode(&msg).unwrap();
        let decoded: TestMessage = MsgPackCodec::decode(&encoded).unwrap();

        assert_eq!(msg, decoded);
    }

    #[cfg(feature = "msgpack")]
    #[test]
    fn test_msgpack_is_compact() {
        let msg = TestMessage {
            id: 1,
            content: "test".to_string(),
        };

        let encoded = MsgPackCodec::encode(&msg).unwrap();
        // MessagePack output should be non-empty binary data
        assert!(!encoded.is_empty());
        // MessagePack is typically more compact than JSON
        let json = serde_json::to_vec(&msg).unwrap();
        assert!(encoded.len() <= json.len());
    }

    #[cfg(feature = "json")]
    #[test]
    fn test_json_encode_decode_roundtrip() {
        let msg = TestMessage {
            id: 42,
            content: "hello world".to_string(),
        };

        let encoded = JsonCodec::encode(&msg).unwrap();
        let decoded: TestMessage = JsonCodec::decode(&encoded).unwrap();

        assert_eq!(msg, decoded);
    }

    #[cfg(feature = "json")]
    #[test]
    fn test_json_is_readable() {
        let msg = TestMessage {
            id: 1,
            content: "test".to_string(),
        };

        let encoded = JsonCodec::encode(&msg).unwrap();
        // JSON output should be valid UTF-8 string
        let json_str = std::str::from_utf8(&encoded).unwrap();
        assert!(json_str.contains("\"id\":1"));
        assert!(json_str.contains("\"content\":\"test\""));
    }
}