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}