heroforge_core/sync/
builder.rs

1//! Sync builder for fluent sync API.
2
3use crate::error::{FossilError, Result};
4use crate::repo::Repository;
5
6/// Protocol to use for sync.
7#[derive(Debug, Clone, PartialEq)]
8pub enum SyncProtocol {
9    /// QUIC protocol (fast, recommended)
10    Quic,
11}
12
13/// Builder for sync operations.
14pub struct SyncBuilder<'a> {
15    repo: &'a Repository,
16    url: Option<String>,
17    username: Option<String>,
18    password: Option<String>,
19}
20
21impl<'a> SyncBuilder<'a> {
22    pub(crate) fn new(repo: &'a Repository) -> Self {
23        Self {
24            repo,
25            url: None,
26            username: None,
27            password: None,
28        }
29    }
30
31    /// Set the remote URL to push to.
32    pub fn to(mut self, url: &str) -> Self {
33        self.url = Some(url.to_string());
34        self
35    }
36
37    /// Set the remote URL to pull from.
38    pub fn from(mut self, url: &str) -> Self {
39        self.url = Some(url.to_string());
40        self
41    }
42
43    /// Set the remote URL for bidirectional sync.
44    pub fn with(mut self, url: &str) -> Self {
45        self.url = Some(url.to_string());
46        self
47    }
48
49    /// Alias for with().
50    pub fn url(self, url: &str) -> Self {
51        self.with(url)
52    }
53
54    /// Set authentication credentials.
55    pub fn auth(mut self, username: &str, password: &str) -> Self {
56        self.username = Some(username.to_string());
57        self.password = Some(password.to_string());
58        self
59    }
60
61    /// Set username.
62    pub fn username(mut self, username: &str) -> Self {
63        self.username = Some(username.to_string());
64        self
65    }
66
67    /// Set password.
68    pub fn password(mut self, password: &str) -> Self {
69        self.password = Some(password.to_string());
70        self
71    }
72
73    /// Normalize URL by removing custom scheme prefixes.
74    fn normalize_url(url: &str) -> String {
75        if url.starts_with("quic://") {
76            url.replacen("quic://", "", 1)
77        } else {
78            url.to_string()
79        }
80    }
81
82    /// Execute push operation (same as sync for QUIC).
83    pub fn push(self) -> Result<SyncResult> {
84        self.execute()
85    }
86
87    /// Execute pull operation (same as sync for QUIC).
88    pub fn pull(self) -> Result<SyncResult> {
89        self.execute()
90    }
91
92    /// Execute bidirectional sync.
93    pub fn execute(self) -> Result<SyncResult> {
94        let url = self
95            .url
96            .ok_or_else(|| FossilError::InvalidArtifact("sync URL is required".to_string()))?;
97        let normalized_url = Self::normalize_url(&url);
98
99        #[cfg(feature = "sync-quic")]
100        {
101            use crate::sync::QuicClient;
102            use std::path::PathBuf;
103
104            let rt = tokio::runtime::Runtime::new()
105                .map_err(|e| FossilError::SyncError(e.to_string()))?;
106            let stats = rt.block_on(async {
107                QuicClient::sync(self.repo, &PathBuf::new(), &normalized_url).await
108            })?;
109            Ok(SyncResult {
110                artifacts_sent: stats.artifacts_sent,
111                artifacts_received: stats.artifacts_received,
112                protocol: SyncProtocol::Quic,
113            })
114        }
115        #[cfg(not(feature = "sync-quic"))]
116        {
117            let _ = (normalized_url, self.repo);
118            Err(FossilError::InvalidArtifact(
119                "QUIC sync requires 'sync-quic' feature".to_string(),
120            ))
121        }
122    }
123}
124
125/// Result of a sync operation.
126#[derive(Debug)]
127pub struct SyncResult {
128    /// Number of artifacts sent.
129    pub artifacts_sent: usize,
130    /// Number of artifacts received.
131    pub artifacts_received: usize,
132    /// Protocol used.
133    pub protocol: SyncProtocol,
134}