rvoip-core 0.3.2

Transport-agnostic spine for RVoIP — types, ConnectionAdapter trait, BridgeManager, Orchestrator entry point
Documentation
use crate::connection::Direction;
use crate::ids::{ConnectionId, ConversationId, MessageId, ParticipantId};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone)]
pub enum MessageOrigin {
    Connection(ConnectionId),
    System,
    Ai(ParticipantId),
}

impl fmt::Debug for MessageOrigin {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::Connection(_) => "MessageOrigin::Connection",
            Self::System => "MessageOrigin::System",
            Self::Ai(_) => "MessageOrigin::Ai",
        })
    }
}

#[derive(Clone)]
pub enum MessageRecipients {
    All,
    Participants(Vec<ParticipantId>),
}

impl fmt::Debug for MessageRecipients {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::All => formatter.write_str("MessageRecipients::All"),
            Self::Participants(participants) => formatter
                .debug_struct("MessageRecipients::Participants")
                .field("count", &participants.len())
                .finish(),
        }
    }
}

#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ContentType {
    Text,
    Json,
    Binary,
    Image,
    Audio,
    Attachment(String),
}

impl fmt::Debug for ContentType {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::Text => "ContentType::Text",
            Self::Json => "ContentType::Json",
            Self::Binary => "ContentType::Binary",
            Self::Image => "ContentType::Image",
            Self::Audio => "ContentType::Audio",
            Self::Attachment(_) => "ContentType::Attachment",
        })
    }
}

#[derive(Clone)]
pub struct Attachment {
    pub url: String,
    pub content_type: ContentType,
    pub size_bytes: u64,
}

impl fmt::Debug for Attachment {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Attachment")
            .field("url_present", &!self.url.is_empty())
            .field("content_type", &self.content_type)
            .field("size_bytes", &self.size_bytes)
            .finish()
    }
}

#[derive(Clone)]
pub struct Message {
    pub id: MessageId,
    pub conversation_id: ConversationId,
    pub origin: MessageOrigin,
    pub from_participant: ParticipantId,
    pub to: MessageRecipients,
    pub direction: Direction,
    pub content_type: ContentType,
    pub body: Bytes,
    pub attachments: Vec<Attachment>,
    pub in_reply_to: Option<MessageId>,
    pub timestamp: DateTime<Utc>,
}

impl fmt::Debug for Message {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Message")
            .field("origin", &self.origin)
            .field("recipients", &self.to)
            .field("direction", &self.direction)
            .field("content_type", &self.content_type)
            .field("body_bytes", &self.body.len())
            .field("attachment_count", &self.attachments.len())
            .field("in_reply_to_present", &self.in_reply_to.is_some())
            .field("timestamp", &self.timestamp)
            .finish()
    }
}

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

    #[test]
    fn message_debug_exposes_shape_without_routing_or_payload_values() {
        let secret = "credential-canary-value";
        let message = Message {
            id: MessageId::new(),
            conversation_id: ConversationId::new(),
            origin: MessageOrigin::Connection(ConnectionId::new()),
            from_participant: ParticipantId::new(),
            to: MessageRecipients::Participants(vec![ParticipantId::new()]),
            direction: Direction::Inbound,
            content_type: ContentType::Attachment(secret.into()),
            body: Bytes::from(secret),
            attachments: vec![Attachment {
                url: format!("https://{secret}.invalid/object"),
                content_type: ContentType::Attachment(secret.into()),
                size_bytes: 23,
            }],
            in_reply_to: Some(MessageId::new()),
            timestamp: Utc::now(),
        };

        let debug = format!("{message:?}");
        assert!(!debug.contains(secret));
        assert!(debug.contains("body_bytes: 23"));
        assert!(debug.contains("attachment_count: 1"));
        assert!(debug.contains("count: 1"));
    }
}