simple-update-in 0.2.0

Immutable nested update with structural sharing
Documentation
//! Path accessors.
//!
//! A path is a list of accessors that describe how to walk into a value. Each
//! step is a string key, a numeric index, or a predicate that selects matching
//! children.

use crate::value::Value;

/// What a predicate sees as the second argument: a map key or an array index.
///
/// A predicate is called with the child value and its location. Maps pass a
/// string key. Arrays pass a numeric index. The selector owns its key, so a
/// closure can match it without fighting a borrowed `&&str`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Selector {
    /// A map key.
    Key(String),
    /// An array index.
    Index(usize),
}

impl Selector {
    /// The key, if this selector is a map key.
    #[must_use]
    pub fn as_key(&self) -> Option<&str> {
        match self {
            Selector::Key(k) => Some(k),
            Selector::Index(_) => None,
        }
    }

    /// The index, if this selector is an array index.
    #[must_use]
    pub fn as_index(&self) -> Option<usize> {
        match self {
            Selector::Index(i) => Some(*i),
            Selector::Key(_) => None,
        }
    }
}

/// A boxed predicate: `(value, key|index) -> bool`.
pub type Predicate = Box<dyn Fn(&Value, &Selector) -> bool>;

/// One step in a path.
pub enum Accessor {
    /// Address a map key. Drives map coercion.
    Key(String),
    /// Address an array index. Drives array coercion.
    Index(usize),
    /// Select matching children. Branches the update across every match.
    Predicate(Predicate),
}

impl Accessor {
    /// Build a key accessor from anything string-like.
    #[must_use]
    pub fn key(k: impl Into<String>) -> Accessor {
        Accessor::Key(k.into())
    }

    /// Build an index accessor.
    #[must_use]
    pub fn index(i: usize) -> Accessor {
        Accessor::Index(i)
    }

    /// Build a predicate accessor from a closure.
    #[must_use]
    pub fn predicate<F>(f: F) -> Accessor
    where
        F: Fn(&Value, &Selector) -> bool + 'static,
    {
        Accessor::Predicate(Box::new(f))
    }
}

impl std::fmt::Debug for Accessor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Accessor::Key(k) => write!(f, "Key({k:?})"),
            Accessor::Index(i) => write!(f, "Index({i})"),
            Accessor::Predicate(_) => write!(f, "Predicate(..)"),
        }
    }
}

/// A resolved step: a key or an index, never a predicate.
///
/// Path resolution turns predicates into the concrete keys and indices they
/// matched. The write phase walks these. This is an internal type. Callers
/// build [`Accessor`] values and never see a `Step`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Step {
    /// A concrete map key.
    Key(String),
    /// A concrete array index.
    Index(usize),
}