simple-update-in 0.2.0

Immutable nested update with structural sharing
Documentation
//! Dynamic JSON-like value model.
//!
//! The update engine walks a tree of maps, arrays, and scalars. This module
//! defines that tree. It mirrors JavaScript value semantics closely enough to
//! reproduce the engine's behavior:
//!
//! - `Undefined` is distinct from `Null`. `Undefined` marks an absent root, a
//!   removal request, and a sparse array hole read back as missing.
//! - Numbers follow `SameValue`. Every `NaN` equals every `NaN`, and `+0.0`
//!   does not equal `-0.0`, matching `Object.is`.
//! - Arrays and maps live behind [`Rc`] so callers can test structural sharing
//!   with [`Value::ptr_eq`]. An unchanged subtree is returned by the same
//!   pointer, never rebuilt.

use std::rc::Rc;

/// A map that keeps keys in insertion order.
///
/// Entries are stored once, as a flat list of pairs. Insertion order is
/// intrinsic to that list. Predicate paths enumerate keys in this order.
/// Equality ignores order, so two maps with the same pairs are equal whatever
/// the order. The maps this library edits are small, so linear lookup is fine
/// and matches how the engine clones whole maps along the path.
#[derive(Clone, Debug, Default)]
pub struct Map {
    entries: Vec<(String, Value)>,
}

impl Map {
    /// Create an empty map.
    #[must_use]
    pub fn new() -> Self {
        Map {
            entries: Vec::new(),
        }
    }

    /// Whether the map has no keys.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// Whether `key` is present.
    #[must_use]
    pub fn contains_key(&self, key: &str) -> bool {
        self.entries.iter().any(|(k, _)| k == key)
    }

    /// Read the value at `key`.
    #[must_use]
    pub fn get(&self, key: &str) -> Option<&Value> {
        self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
    }

    /// Insert or replace `key`. A new key is appended in insertion order. An
    /// existing key keeps its position.
    pub fn insert(&mut self, key: String, value: Value) {
        if let Some(entry) = self.entries.iter_mut().find(|(k, _)| *k == key) {
            entry.1 = value;
        } else {
            self.entries.push((key, value));
        }
    }

    /// Remove `key` and its value, keeping the order of the rest.
    pub fn remove(&mut self, key: &str) {
        self.entries.retain(|(k, _)| k != key);
    }

    /// Iterate keys and values in insertion order.
    pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
        self.entries.iter().map(|(k, v)| (k, v))
    }
}

impl PartialEq for Map {
    /// Two maps are equal when they hold the same keys and values. Order is
    /// ignored, matching deep value equality.
    fn eq(&self, other: &Self) -> bool {
        if self.entries.len() != other.entries.len() {
            return false;
        }
        self.entries.iter().all(|(k, v)| other.get(k) == Some(v))
    }
}

impl FromIterator<(String, Value)> for Map {
    fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
        let mut map = Map::new();
        for (k, v) in iter {
            map.insert(k, v);
        }
        map
    }
}

impl IntoIterator for Map {
    type Item = (String, Value);
    type IntoIter = std::vec::IntoIter<(String, Value)>;

    fn into_iter(self) -> Self::IntoIter {
        self.entries.into_iter()
    }
}

/// A JSON-like value.
///
/// Scalars are owned. `Array` and `Object` share their contents through [`Rc`]
/// so an unchanged subtree can be returned by pointer.
#[derive(Clone, Debug)]
pub enum Value {
    /// JavaScript `undefined`. Absent root, removal sentinel, or a read-back
    /// sparse hole.
    Undefined,
    /// JavaScript `null`. A real value, treated as a map for type coercion.
    Null,
    /// A boolean.
    Bool(bool),
    /// A number. Carries the raw `f64` so `-0.0` and `NaN` survive.
    Number(f64),
    /// A string.
    String(String),
    /// An array. Sparse holes are stored as `Undefined`, since a JavaScript
    /// hole reads back as `undefined`.
    Array(Rc<Vec<Value>>),
    /// A map of string keys to values.
    Object(Rc<Map>),
}

impl Value {
    /// Whether this is `Undefined`.
    #[must_use]
    pub fn is_undefined(&self) -> bool {
        matches!(self, Value::Undefined)
    }

    /// Borrow the array contents, if this is an array.
    #[must_use]
    pub fn as_array(&self) -> Option<&[Value]> {
        match self {
            Value::Array(items) => Some(items),
            _ => None,
        }
    }

    /// Borrow the map contents, if this is an object.
    #[must_use]
    pub fn as_object(&self) -> Option<&Map> {
        match self {
            Value::Object(map) => Some(map),
            _ => None,
        }
    }

    /// Build an array value from items.
    #[must_use]
    pub fn array(items: Vec<Value>) -> Value {
        Value::Array(Rc::new(items))
    }

    /// Build a map value from a [`Map`].
    #[must_use]
    pub fn object(map: Map) -> Value {
        Value::Object(Rc::new(map))
    }

    /// Build a map value from key and value pairs, in order.
    ///
    /// Convenience for tests and call sites that hold literal data.
    #[must_use]
    pub fn from_pairs<K, I>(pairs: I) -> Value
    where
        K: Into<String>,
        I: IntoIterator<Item = (K, Value)>,
    {
        let map = pairs.into_iter().map(|(k, v)| (k.into(), v)).collect();
        Value::object(map)
    }

    /// Pointer equality for shared containers.
    ///
    /// Returns `true` when both values are the same array or the same map by
    /// [`Rc`] pointer. This is the structural-sharing check: an untouched
    /// subtree returns its original pointer, so callers can detect "no change"
    /// cheaply. Scalars never share, so this returns `false` for them.
    #[must_use]
    pub fn ptr_eq(&self, other: &Value) -> bool {
        match (self, other) {
            (Value::Array(a), Value::Array(b)) => Rc::ptr_eq(a, b),
            (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
            _ => false,
        }
    }

    /// `SameValue` equality, the `Object.is` algorithm.
    ///
    /// Numbers follow `SameValue`: every `NaN` equals every `NaN` regardless of
    /// bit pattern, and `+0.0` does not equal `-0.0`. Containers compare by
    /// pointer, which is what the engine's no-op check needs. This is not deep
    /// equality. Two distinct arrays with equal contents are not `SameValue`
    /// equal.
    #[must_use]
    pub fn same_value(&self, other: &Value) -> bool {
        match (self, other) {
            (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
            (Value::Bool(a), Value::Bool(b)) => a == b,
            (Value::Number(a), Value::Number(b)) => {
                (a.is_nan() && b.is_nan()) || a.to_bits() == b.to_bits()
            }
            (Value::String(a), Value::String(b)) => a == b,
            (Value::Array(_), Value::Array(_)) | (Value::Object(_), Value::Object(_)) => {
                self.ptr_eq(other)
            }
            _ => false,
        }
    }
}

impl From<f64> for Value {
    fn from(n: f64) -> Value {
        Value::Number(n)
    }
}

impl From<f32> for Value {
    fn from(n: f32) -> Value {
        Value::Number(f64::from(n))
    }
}

impl From<i32> for Value {
    fn from(n: i32) -> Value {
        Value::Number(f64::from(n))
    }
}

impl From<u32> for Value {
    fn from(n: u32) -> Value {
        Value::Number(f64::from(n))
    }
}

impl From<bool> for Value {
    fn from(b: bool) -> Value {
        Value::Bool(b)
    }
}

impl From<&str> for Value {
    fn from(s: &str) -> Value {
        Value::String(s.to_string())
    }
}

impl From<String> for Value {
    fn from(s: String) -> Value {
        Value::String(s)
    }
}

impl From<Vec<Value>> for Value {
    fn from(items: Vec<Value>) -> Value {
        Value::array(items)
    }
}

impl From<Map> for Value {
    fn from(map: Map) -> Value {
        Value::object(map)
    }
}

impl PartialEq for Value {
    /// Deep value equality.
    ///
    /// Numbers follow `SameValue`, so `NaN == NaN` holds for any two `NaN` and
    /// tests can assert on it. This matches `same_value` on scalars and is
    /// deliberate. Do not switch the `Number` arm to plain `==`, or `NaN`
    /// assertions break. Arrays and maps compare element by element, which is
    /// where this differs from `same_value`: two distinct arrays with equal
    /// contents are equal here but not under `same_value`. This is the equality
    /// `assert_eq!` uses in tests, not the engine's no-op check.
    fn eq(&self, other: &Value) -> bool {
        match (self, other) {
            (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
            (Value::Bool(a), Value::Bool(b)) => a == b,
            (Value::Number(a), Value::Number(b)) => {
                (a.is_nan() && b.is_nan()) || a.to_bits() == b.to_bits()
            }
            (Value::String(a), Value::String(b)) => a == b,
            (Value::Array(a), Value::Array(b)) => a == b,
            (Value::Object(a), Value::Object(b)) => a == b,
            _ => false,
        }
    }
}