Skip to main content

ma_core/
outbox.rs

1//! Transport-agnostic send handle to a remote ma service.
2//!
3//! An `Outbox` wraps the transport details and exposes only
4//! `send()` + `close()`.
5//!
6//! `send()` takes a [`Message`], validates it,
7//! serializes to CBOR, and transmits. Malformed or expired messages
8//! are rejected before anything hits the wire.
9//!
10//! Requires the `iroh` feature.
11//!
12//! ```ignore
13//! let mut outbox = ep.outbox("did:ma:456", "ma/presence/0.0.1").await?;
14//! outbox.send(&message).await?;
15//! outbox.close();
16//! ```
17
18use crate::error::{Error, Result};
19use crate::Message;
20use async_trait::async_trait;
21
22#[async_trait]
23pub(crate) trait OutboxWire: Send + std::fmt::Debug {
24    async fn send_payload(&mut self, payload: &[u8]) -> Result<()>;
25    fn close_box(self: Box<Self>);
26}
27
28/// A transport-agnostic write handle to a remote service.
29///
30/// The caller doesn't need to know the underlying transport.
31#[derive(Debug)]
32pub struct Outbox {
33    inner: Option<Box<dyn OutboxWire>>,
34    did: String,
35    protocol: String,
36}
37
38impl Outbox {
39    /// Create an outbox backed by a transport implementation.
40    pub(crate) fn from_transport<T>(transport: T, did: String, protocol: String) -> Self
41    where
42        T: OutboxWire + 'static,
43    {
44        Self {
45            inner: Some(Box::new(transport)),
46            did,
47            protocol,
48        }
49    }
50
51    /// Send a ma message to the remote service.
52    ///
53    /// Validates the message headers, serializes to CBOR, and transmits.
54    ///
55    /// # Errors
56    /// Returns an error if validation, serialization, or transport send fails.
57    pub async fn send(&mut self, message: &Message) -> Result<()> {
58        message.headers().validate()?;
59        let cbor = message.to_cbor()?;
60        match self.inner.as_mut() {
61            Some(transport) => transport.send_payload(&cbor).await,
62            None => Err(Error::ConnectionClosed("outbox is closed".to_string())),
63        }
64    }
65
66    /// The DID this outbox delivers to.
67    #[must_use]
68    pub fn did(&self) -> &str {
69        &self.did
70    }
71
72    /// The protocol this outbox is connected to.
73    #[must_use]
74    pub fn protocol(&self) -> &str {
75        &self.protocol
76    }
77
78    /// Close the outbox gracefully.
79    pub fn close(mut self) {
80        if let Some(transport) = self.inner.take() {
81            transport.close_box();
82        }
83    }
84}