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.encode()?;
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}