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