braid_http_rs 0.1.5

Unified Braid Protocol implementation in Rust, including Braid-HTTP, Antimatter CRDT, and BraidFS.
Documentation
use crate::core::merge::merge_type::{MergePatch, MergeResult, MergeType};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Debug;

/// Simpleton merge-type implementation.
///
/// Matches the behavior of braid-text's simpleton-client.js.
#[derive(Debug, Clone)]
pub struct SimpletonMergeType {
    peer_id: String,
    content: String,
    version: Vec<String>,
    seq: u64,
}

impl SimpletonMergeType {
    pub fn new(peer_id: &str) -> Self {
        Self {
            peer_id: peer_id.to_string(),
            content: String::new(),
            version: Vec::new(),
            seq: 0,
        }
    }

    fn generate_version(&mut self) -> String {
        self.seq += 1;
        format!("{}@{}", self.seq, self.peer_id)
    }

    fn simple_diff(&self, old_text: &str, new_text: &str) -> (usize, usize, String) {
        let a: Vec<char> = old_text.chars().collect();
        let b: Vec<char> = new_text.chars().collect();

        // Common prefix
        let mut p = 0;
        let len = std::cmp::min(a.len(), b.len());
        while p < len && a[p] == b[p] {
            p += 1;
        }

        // Common suffix
        let mut s = 0;
        let len_remaining = std::cmp::min(a.len() - p, b.len() - p);
        while s < len_remaining && a[a.len() - s - 1] == b[b.len() - s - 1] {
            s += 1;
        }

        let range_start = p;
        let range_end = a.len() - s;
        let content: String = b[p..b.len() - s].iter().collect();

        (range_start, range_end, content)
    }
}

impl MergeType for SimpletonMergeType {
    fn name(&self) -> &str {
        "simpleton"
    }

    fn initialize(&mut self, content: &str) -> MergeResult {
        self.content = content.to_string();
        let version = self.generate_version();
        self.version = vec![version.clone()];
        MergeResult::success(Some(version), Vec::new())
    }

    fn apply_patch(&mut self, patch: MergePatch) -> MergeResult {
        let mut offset: isize = 0;
        let mut last_version = None;

        let range_regex = regex::Regex::new(r"\[(\d+):(\d+)\]").unwrap();
        if let Some(caps) = range_regex.captures(&patch.range) {
            let start_idx: usize = caps[1].parse().unwrap_or(0);
            let end_idx: usize = caps[2].parse().unwrap_or(0);

            let content_str = match &patch.content {
                Value::String(s) => s.clone(),
                v => v.to_string(),
            };

            let chars: Vec<char> = self.content.chars().collect();

            // Adjust start/end with current offset
            let start = (start_idx as isize + offset).max(0) as usize;
            let end = (end_idx as isize + offset).max(0) as usize;

            if start <= chars.len() && end <= chars.len() && start <= end {
                let mut new_chars = chars[..start].to_vec();
                new_chars.extend(content_str.chars());
                new_chars.extend(&chars[end..]);

                let deleted_count = end - start;
                let added_count = content_str.chars().count();
                offset += added_count as isize - deleted_count as isize;

                self.content = new_chars.into_iter().collect();
                if let Some(v) = patch.version.clone() {
                    last_version = Some(v);
                }
            } else {
                return MergeResult::failure(&format!(
                    "Invalid range [{} : {}] for content length {}",
                    start,
                    end,
                    chars.len()
                ));
            }
        }

        if let Some(v) = last_version {
            self.version = vec![v.clone()];
        }

        MergeResult::success(patch.version, Vec::new())
    }

    fn local_edit(&mut self, mut patch: MergePatch) -> MergeResult {
        let version = self.generate_version();
        patch.version = Some(version.clone());

        // For simpleton, we expect the user to provide the full content or a diff
        // If it's a diff in [start:end] format, we use it. 
        // If it's just content, we replace everything? No, usually local_edit provides the change.
        
        // This is a simplified implementation. Real simpleton-client.js does more.
        // But for now, we follow the trait.
        
        self.apply_patch(patch.clone())
    }

    fn get_content(&self) -> String {
        self.content.clone()
    }

    fn get_version(&self) -> Vec<String> {
        self.version.clone()
    }

    fn get_all_versions(&self) -> HashMap<String, Vec<String>> {
        let mut map = HashMap::new();
        for v in &self.version {
            map.insert(v.clone(), Vec::new());
        }
        map
    }

    fn prune(&mut self) -> bool {
        false
    }

    fn clone_box(&self) -> Box<dyn MergeType> {
        Box::new(self.clone())
    }
}