Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/sync/transports/
mod.rs

1//! Transport abstractions for sync communication.
2//!
3//! This module defines the transport trait that different network
4//! implementations must implement, allowing the sync module to
5//! work over various protocols (HTTP, Iroh, Bluetooth, etc.).
6
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use serde::{Serialize, de::DeserializeOwned};
11
12use crate::{
13    Entry, Result,
14    crdt::Doc,
15    store::Registered,
16    sync::{
17        SyncError,
18        handler::SyncHandler,
19        peer_types::Address,
20        protocol::{SyncRequest, SyncResponse},
21    },
22};
23
24pub mod http;
25pub mod iroh;
26pub mod shared;
27
28/// Configuration that can be persisted for a transport.
29///
30/// Each transport implementation can define its own configuration type
31/// that implements this trait. The configuration is stored in the `_sync`
32/// database's `transports` subtree and loaded when the transport is enabled.
33///
34/// Transport configs must also implement [`Registered`] to provide their type identifier.
35///
36/// # Example
37///
38/// ```ignore
39/// use serde::{Serialize, Deserialize};
40/// use eidetica::store::Registered;
41/// use eidetica::sync::transports::TransportConfig;
42///
43/// #[derive(Clone, Serialize, Deserialize, Default)]
44/// pub struct MyTransportConfig {
45///     pub some_setting: String,
46/// }
47///
48/// impl Registered for MyTransportConfig {
49///     fn type_id() -> &'static str {
50///         "my-transport:v0"
51///     }
52/// }
53///
54/// impl TransportConfig for MyTransportConfig {}
55/// ```
56pub trait TransportConfig:
57    Registered + Serialize + DeserializeOwned + Default + Clone + Send + Sync
58{
59}
60
61/// Builder for creating transports with persisted state.
62///
63/// Each transport implementation should provide a builder that implements this trait.
64/// The builder configures the transport, and the `build()` method creates the transport
65/// using persisted state (loaded by name from the sync database).
66///
67/// # Persisted State
68///
69/// Each named transport instance has its own persisted [`Doc`] that stores state
70/// that should survive restarts (e.g., cryptographic keys for identity).
71/// The `build()` method receives this state and can update it (e.g., generating
72/// a new key on first run). The updated state is saved after the transport is created.
73///
74/// # Example
75///
76/// ```ignore
77/// use async_trait::async_trait;
78/// use eidetica::{Result, crdt::Doc};
79/// use eidetica::sync::transports::{TransportBuilder, SyncTransport};
80///
81/// pub struct MyTransportBuilder {
82///     pub some_setting: String,
83/// }
84///
85/// #[async_trait]
86/// impl TransportBuilder for MyTransportBuilder {
87///     type Transport = MyTransport;
88///
89///     async fn build(self, persisted: Doc) -> Result<(Self::Transport, Option<Doc>)> {
90///         let transport = MyTransport::new(self.some_setting);
91///         Ok((transport, None)) // No state to persist
92///     }
93/// }
94/// ```
95#[async_trait]
96pub trait TransportBuilder: Send {
97    /// The transport type this builder creates.
98    type Transport: SyncTransport;
99
100    /// Build the transport from persisted state.
101    ///
102    /// # Arguments
103    /// * `persisted` - The persisted state for this named transport instance.
104    ///   May be empty on first run.
105    ///
106    /// # Returns
107    /// A tuple of (transport, optional_updated_state). If `Some(doc)`, the state
108    /// will be saved to the sync database. If `None`, no save is performed.
109    async fn build(self, persisted: Doc) -> Result<(Self::Transport, Option<Doc>)>;
110}
111
112/// Trait for implementing sync communication over different transports.
113///
114/// Each transport implementation (HTTP, Iroh, etc.) must
115/// implement this trait to provide server and client functionality.
116#[async_trait]
117pub trait SyncTransport: Send + Sync {
118    /// Get the transport type identifier.
119    ///
120    /// This should return a unique string identifying the transport type
121    /// (e.g., "http", "iroh"). Used for routing and configuration lookup.
122    fn transport_type(&self) -> &'static str;
123
124    /// Check if this transport can handle the given address
125    ///
126    /// # Arguments
127    /// * `address` - The address to check
128    ///
129    /// # Returns
130    /// True if this transport can handle the address, false otherwise.
131    fn can_handle_address(&self, address: &Address) -> bool;
132
133    /// Start a server with the pre-configured bind address.
134    ///
135    /// The transport uses its bind address configured via the builder.
136    ///
137    /// # Arguments
138    /// * `handler` - The sync handler to process incoming requests
139    ///
140    /// # Returns
141    /// A Result indicating success or failure of server startup.
142    async fn start_server(&mut self, handler: Arc<dyn SyncHandler>) -> Result<()>;
143
144    /// Stop the running server gracefully.
145    ///
146    /// # Returns
147    /// A Result indicating success or failure of server shutdown.
148    async fn stop_server(&mut self) -> Result<()>;
149
150    /// Send a sync request to a peer and receive a response.
151    ///
152    /// # Arguments
153    /// * `address` - The address of the peer to connect to
154    /// * `request` - The sync request to send
155    ///
156    /// # Returns
157    /// The response from the peer, or an error if the request failed.
158    async fn send_request(&self, address: &Address, request: &SyncRequest) -> Result<SyncResponse>;
159
160    /// Send entries to a sync peer and ensure they are acknowledged.
161    ///
162    /// This is a convenience method that wraps send_request and validates the response.
163    ///
164    /// # Arguments
165    /// * `address` - The address of the peer to connect to
166    /// * `entries` - The entries to send
167    ///
168    /// # Returns
169    /// A Result indicating whether the entries were successfully acknowledged.
170    async fn send_entries(&self, address: &Address, entries: &[Entry]) -> Result<()> {
171        let request = SyncRequest::SendEntries(entries.to_vec());
172        let response = self.send_request(address, &request).await?;
173        match response {
174            SyncResponse::Ack | SyncResponse::Count(_) => Ok(()),
175            SyncResponse::Error(msg) => Err(SyncError::Network(msg).into()),
176            _ => Err(SyncError::UnexpectedResponse {
177                expected: "Ack or Count",
178                actual: format!("{response:?}"),
179            }
180            .into()),
181        }
182    }
183
184    /// Check if the server is currently running.
185    ///
186    /// # Returns
187    /// True if the server is running, false otherwise.
188    fn is_server_running(&self) -> bool;
189
190    /// Get the address the server is currently bound to.
191    ///
192    /// # Returns
193    /// The server address if running, or an error if no server is running.
194    /// Useful when the server was started with port 0 for dynamic port assignment.
195    fn get_server_address(&self) -> Result<String>;
196}