colla 0.1.0

Operational Transformation library for nested documents with text and rich-text
Documentation
use std::collections::BTreeMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::sync::Arc;

use crate::error::{CodecError, ValueError};
use crate::input_limits::InputLimits;
use crate::path::{Path, PathSeg};
use crate::richtext::RichText;

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FiniteF64(u64);

impl FiniteF64 {
    pub fn new(value: f64) -> Result<Self, ValueError> {
        if !value.is_finite() {
            return Err(ValueError::NonFiniteFloat);
        }
        let canonical = if value == 0.0 { 0.0 } else { value };
        Ok(Self(canonical.to_bits()))
    }

    pub fn get(self) -> f64 {
        f64::from_bits(self.0)
    }
}

impl fmt::Debug for FiniteF64 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.get().fmt(f)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Text(Arc<str>);

impl Text {
    pub fn new(value: impl Into<String>) -> Self {
        Self(Arc::from(value.into()))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn len(&self) -> usize {
        self.0.chars().count()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct List(Arc<Vec<Value>>);

impl List {
    pub fn new(values: Vec<Value>) -> Self {
        Self(Arc::new(values))
    }

    pub fn as_slice(&self) -> &[Value] {
        &self.0
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn get(&self, index: usize) -> Option<&Value> {
        self.0.get(index)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Map(Arc<BTreeMap<String, Value>>);

impl Hash for Map {
    fn hash<H: Hasher>(&self, state: &mut H) {
        for (key, value) in self.0.iter() {
            key.hash(state);
            value.hash(state);
        }
    }
}

impl Map {
    pub fn from_entries<I, K>(entries: I) -> Result<Self, ValueError>
    where
        I: IntoIterator<Item = (K, Value)>,
        K: Into<String>,
    {
        let mut map = BTreeMap::new();
        for (key, value) in entries {
            let key = key.into();
            if map.insert(key.clone(), value).is_some() {
                return Err(ValueError::DuplicateKey(key));
            }
        }
        Ok(Self(Arc::new(map)))
    }

    pub(crate) fn from_btree(map: BTreeMap<String, Value>) -> Self {
        Self(Arc::new(map))
    }

    pub fn get(&self, key: &str) -> Option<&Value> {
        self.0.get(key)
    }

    pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
        self.0.iter()
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub(crate) fn to_btree(&self) -> BTreeMap<String, Value> {
        self.0.as_ref().clone()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValueType {
    Null,
    Bool,
    Int,
    Float,
    String,
    Text,
    RichText,
    List,
    Map,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ValueKind {
    Null,
    Bool(bool),
    Int(i64),
    Float(FiniteF64),
    String(Arc<str>),
    Text(Text),
    RichText(RichText),
    List(List),
    Map(Map),
}

#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Value(Arc<ValueKind>);

impl fmt::Debug for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Value {
    pub fn null() -> Self {
        Self(Arc::new(ValueKind::Null))
    }
    pub fn bool(value: bool) -> Self {
        Self(Arc::new(ValueKind::Bool(value)))
    }
    pub fn int(value: i64) -> Self {
        Self(Arc::new(ValueKind::Int(value)))
    }
    pub fn float(value: f64) -> Result<Self, ValueError> {
        Ok(Self(Arc::new(ValueKind::Float(FiniteF64::new(value)?))))
    }
    pub fn finite_float(value: FiniteF64) -> Self {
        Self(Arc::new(ValueKind::Float(value)))
    }
    pub fn string(value: impl Into<String>) -> Self {
        Self(Arc::new(ValueKind::String(Arc::from(value.into()))))
    }
    pub fn text(value: impl Into<String>) -> Self {
        Self(Arc::new(ValueKind::Text(Text::new(value))))
    }
    pub fn rich_text(value: RichText) -> Self {
        Self(Arc::new(ValueKind::RichText(value)))
    }
    pub fn list<I>(values: I) -> Self
    where
        I: IntoIterator<Item = Value>,
    {
        Self(Arc::new(ValueKind::List(List::new(
            values.into_iter().collect(),
        ))))
    }
    pub fn map<I, K>(entries: I) -> Result<Self, ValueError>
    where
        I: IntoIterator<Item = (K, Value)>,
        K: Into<String>,
    {
        Ok(Self(Arc::new(ValueKind::Map(Map::from_entries(entries)?))))
    }
    pub(crate) fn from_kind(kind: ValueKind) -> Self {
        Self(Arc::new(kind))
    }

    pub fn kind(&self) -> &ValueKind {
        &self.0
    }
    pub fn value_type(&self) -> ValueType {
        match self.kind() {
            ValueKind::Null => ValueType::Null,
            ValueKind::Bool(_) => ValueType::Bool,
            ValueKind::Int(_) => ValueType::Int,
            ValueKind::Float(_) => ValueType::Float,
            ValueKind::String(_) => ValueType::String,
            ValueKind::Text(_) => ValueType::Text,
            ValueKind::RichText(_) => ValueType::RichText,
            ValueKind::List(_) => ValueType::List,
            ValueKind::Map(_) => ValueType::Map,
        }
    }

    pub fn as_map(&self) -> Option<&Map> {
        if let ValueKind::Map(v) = self.kind() {
            Some(v)
        } else {
            None
        }
    }
    pub fn as_list(&self) -> Option<&List> {
        if let ValueKind::List(v) = self.kind() {
            Some(v)
        } else {
            None
        }
    }
    pub fn as_text(&self) -> Option<&Text> {
        if let ValueKind::Text(v) = self.kind() {
            Some(v)
        } else {
            None
        }
    }
    pub fn as_rich_text(&self) -> Option<&RichText> {
        if let ValueKind::RichText(v) = self.kind() {
            Some(v)
        } else {
            None
        }
    }
    pub fn as_int(&self) -> Option<i64> {
        if let ValueKind::Int(v) = self.kind() {
            Some(*v)
        } else {
            None
        }
    }

    pub fn get(&self, path: &Path) -> Option<&Value> {
        let mut current = self;
        for segment in path.segments() {
            current = match (current.kind(), segment) {
                (ValueKind::Map(map), PathSeg::Key(key)) => map.get(key)?,
                (ValueKind::List(list), PathSeg::Index(index)) => list.get(*index)?,
                _ => return None,
            };
        }
        Some(current)
    }

    pub(crate) fn check_input_limits(&self, limits: &InputLimits) -> Result<(), CodecError> {
        let mut stack = vec![(self, 1usize)];
        let mut nodes = 0usize;
        while let Some((value, depth)) = stack.pop() {
            nodes += 1;
            if nodes > limits.max_value_nodes {
                return Err(CodecError::LimitExceeded {
                    name: "value nodes",
                    actual: nodes,
                    limit: limits.max_value_nodes,
                });
            }
            if depth > limits.max_depth {
                return Err(CodecError::LimitExceeded {
                    name: "value depth",
                    actual: depth,
                    limit: limits.max_depth,
                });
            }
            match value.kind() {
                ValueKind::String(s) => {
                    check_len("string bytes", s.len(), limits.max_string_bytes)?
                }
                ValueKind::Text(t) => {
                    check_len("text bytes", t.as_str().len(), limits.max_string_bytes)?
                }
                ValueKind::List(list) => {
                    check_len("container length", list.len(), limits.max_container_len)?;
                    for child in list.as_slice().iter().rev() {
                        stack.push((child, depth + 1));
                    }
                }
                ValueKind::Map(map) => {
                    check_len("container length", map.len(), limits.max_container_len)?;
                    for (key, child) in map.iter() {
                        check_len("string bytes", key.len(), limits.max_string_bytes)?;
                        stack.push((child, depth + 1));
                    }
                }
                ValueKind::RichText(rich) => {
                    check_len(
                        "container length",
                        rich.spans().len(),
                        limits.max_container_len,
                    )?;
                    for span in rich.spans().iter().rev() {
                        match &span.content {
                            crate::richtext::RichInsert::Text(text) => {
                                check_len("string bytes", text.len(), limits.max_string_bytes)?;
                            }
                            crate::richtext::RichInsert::Embed(child) => {
                                stack.push((child, depth + 1));
                            }
                        }
                        check_len(
                            "container length",
                            span.attrs.len(),
                            limits.max_container_len,
                        )?;
                        for (key, value) in span.attrs.iter() {
                            check_len("string bytes", key.len(), limits.max_string_bytes)?;
                            if let crate::AttrValue::String(value) = value {
                                check_len("string bytes", value.len(), limits.max_string_bytes)?;
                            }
                        }
                    }
                }
                _ => {}
            }
        }
        Ok(())
    }
}

fn check_len(name: &'static str, actual: usize, limit: usize) -> Result<(), CodecError> {
    if actual > limit {
        Err(CodecError::LimitExceeded {
            name,
            actual,
            limit,
        })
    } else {
        Ok(())
    }
}