heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! Sync builder for fluent sync API.

use crate::error::{FossilError, Result};
use crate::repo::Repository;

/// Protocol to use for sync.
#[derive(Debug, Clone, PartialEq)]
pub enum SyncProtocol {
    /// QUIC protocol (fast, recommended)
    Quic,
}

/// Builder for sync operations.
pub struct SyncBuilder<'a> {
    repo: &'a Repository,
    url: Option<String>,
    username: Option<String>,
    password: Option<String>,
}

impl<'a> SyncBuilder<'a> {
    pub(crate) fn new(repo: &'a Repository) -> Self {
        Self {
            repo,
            url: None,
            username: None,
            password: None,
        }
    }

    /// Set the remote URL to push to.
    pub fn to(mut self, url: &str) -> Self {
        self.url = Some(url.to_string());
        self
    }

    /// Set the remote URL to pull from.
    pub fn from(mut self, url: &str) -> Self {
        self.url = Some(url.to_string());
        self
    }

    /// Set the remote URL for bidirectional sync.
    pub fn with(mut self, url: &str) -> Self {
        self.url = Some(url.to_string());
        self
    }

    /// Alias for with().
    pub fn url(self, url: &str) -> Self {
        self.with(url)
    }

    /// Set authentication credentials.
    pub fn auth(mut self, username: &str, password: &str) -> Self {
        self.username = Some(username.to_string());
        self.password = Some(password.to_string());
        self
    }

    /// Set username.
    pub fn username(mut self, username: &str) -> Self {
        self.username = Some(username.to_string());
        self
    }

    /// Set password.
    pub fn password(mut self, password: &str) -> Self {
        self.password = Some(password.to_string());
        self
    }

    /// Normalize URL by removing custom scheme prefixes.
    fn normalize_url(url: &str) -> String {
        if url.starts_with("quic://") {
            url.replacen("quic://", "", 1)
        } else {
            url.to_string()
        }
    }

    /// Execute push operation (same as sync for QUIC).
    pub fn push(self) -> Result<SyncResult> {
        self.execute()
    }

    /// Execute pull operation (same as sync for QUIC).
    pub fn pull(self) -> Result<SyncResult> {
        self.execute()
    }

    /// Execute bidirectional sync.
    pub fn execute(self) -> Result<SyncResult> {
        let url = self
            .url
            .ok_or_else(|| FossilError::InvalidArtifact("sync URL is required".to_string()))?;
        let normalized_url = Self::normalize_url(&url);

        #[cfg(feature = "sync-quic")]
        {
            use crate::sync::QuicClient;
            use std::path::PathBuf;

            let rt = tokio::runtime::Runtime::new()
                .map_err(|e| FossilError::SyncError(e.to_string()))?;
            let stats = rt.block_on(async {
                QuicClient::sync(self.repo, &PathBuf::new(), &normalized_url).await
            })?;
            Ok(SyncResult {
                artifacts_sent: stats.artifacts_sent,
                artifacts_received: stats.artifacts_received,
                protocol: SyncProtocol::Quic,
            })
        }
        #[cfg(not(feature = "sync-quic"))]
        {
            let _ = (normalized_url, self.repo);
            Err(FossilError::InvalidArtifact(
                "QUIC sync requires 'sync-quic' feature".to_string(),
            ))
        }
    }
}

/// Result of a sync operation.
#[derive(Debug)]
pub struct SyncResult {
    /// Number of artifacts sent.
    pub artifacts_sent: usize,
    /// Number of artifacts received.
    pub artifacts_received: usize,
    /// Protocol used.
    pub protocol: SyncProtocol,
}