saddle-core 0.3.36

Shared contracts for Saddle components
Documentation
//! Borrowed JSON contract for context owners. Ownership and allocation bills
//! stay with the provider; observing a subtree grants no mutation capability.
use serde::{Serialize, Serializer, ser::{SerializeMap, SerializeSeq}};

pub enum JsonKind<'a> {
    Null,
    Bool(bool),
    String(&'a str),
    Number(&'a str),
    Array,
    Object,
}

/// Implementations expose a validated immutable tree using owner-local indices.
/// Numbers are original JSON tokens. Object children carry decoded keys;
/// array children carry no key. `after` is the previously returned child index.
pub trait ContextJson {
    fn kind(&self, index: usize) -> JsonKind<'_>;
    fn child(&self, parent: usize, after: Option<usize>) -> Option<(Option<&str>, usize)>;
}

/// Shared immutable provider. The callback runs synchronously under the
/// provider's read guard; no tree borrow or mutable owner can escape it.
pub trait ContextSource: Send + Sync {
    /// Providers with prepaid shared storage release the physical Arc before
    /// destroying its allocation bill. Every erased handle calls this hook.
    fn release(self: std::sync::Arc<Self>) { drop(self); }
    fn with_tree(&self, visitor: &mut dyn FnMut(JsonSubtree<'_>));
    /// Absolute schema paths explicitly marked secret by the source owner.
    fn secret_paths(&self) -> &[String] { &[] }
    /// Borrow a tree and its policy under the same provider read guard.
    /// Switching Live/Frozen sources override this callback; borrows cannot
    /// escape the callback or outlive the source's synchronized state.
    fn with_tree_and_secrets(&self, visitor: &mut dyn FnMut(JsonSubtree<'_>, &[String])) {
        self.with_tree(&mut |tree| visitor(tree, self.secret_paths()));
    }
    fn with_secrets(&self, visitor: &mut dyn FnMut(&[String])) { visitor(self.secret_paths()); }
}

/// Shared provider reference whose final release remains provider-controlled.
pub struct ContextSourceRef(Option<std::sync::Arc<dyn ContextSource>>);
impl ContextSourceRef {
    pub fn new<T: ContextSource + 'static>(source: std::sync::Arc<T>) -> Self { Self(Some(source)) }
}
impl From<std::sync::Arc<dyn ContextSource>> for ContextSourceRef {
    fn from(source: std::sync::Arc<dyn ContextSource>) -> Self { Self(Some(source)) }
}
impl<T: ContextSource + 'static> From<std::sync::Arc<T>> for ContextSourceRef {
    fn from(source: std::sync::Arc<T>) -> Self { Self::new(source) }
}
impl AsRef<dyn ContextSource> for ContextSourceRef {
    fn as_ref(&self) -> &(dyn ContextSource + 'static) { self.0.as_deref().expect("live source") }
}
impl Clone for ContextSourceRef {
    fn clone(&self) -> Self { Self(self.0.clone()) }
}
impl std::ops::Deref for ContextSourceRef {
    type Target = dyn ContextSource;
    fn deref(&self) -> &Self::Target { self.0.as_deref().expect("live source") }
}
impl Drop for ContextSourceRef {
    fn drop(&mut self) { if let Some(source) = self.0.take() { source.release(); } }
}

#[derive(Clone, Copy)]
pub struct JsonSubtree<'a> {
    owner: &'a dyn ContextJson,
    index: usize,
}
impl<'a> JsonSubtree<'a> {
    pub fn same_node(self, other: Self) -> bool {
        std::ptr::eq(self.owner, other.owner) && self.index == other.index
    }
    pub fn new(owner: &'a dyn ContextJson, index: usize) -> Self { Self { owner, index } }
    pub fn kind(self) -> JsonKind<'a> { self.owner.kind(self.index) }
    pub fn children(self) -> impl Iterator<Item = (Option<&'a str>, Self)> {
        let mut previous = None;
        std::iter::from_fn(move || {
            let (key, index) = self.owner.child(self.index, previous)?;
            previous = Some(index);
            Some((key, Self::new(self.owner, index)))
        })
    }
    pub fn pointer(self, path: &str) -> Result<Option<Self>, crate::json_pointer::InvalidJsonPointer> {
        let mut current = self;
        for segment in crate::json_pointer::JsonPointer::parse(path)?.segments() {
            let next = match current.kind() {
                JsonKind::Object => current.children()
                    .find(|(key, _)| key.is_some_and(|key| segment.matches(key))).map(|(_, child)| child),
                JsonKind::Array => segment.array_index()
                    .and_then(|index| current.children().nth(index)).map(|(_, child)| child),
                _ => None,
            };
            let Some(next) = next else { return Ok(None); };
            current = next;
        }
        Ok(Some(current))
    }
}
impl Serialize for JsonSubtree<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match self.kind() {
            JsonKind::Null => serializer.serialize_none(),
            JsonKind::Bool(value) => serializer.serialize_bool(value),
            JsonKind::String(value) => serializer.serialize_str(value),
            JsonKind::Number(value) => {
                let raw: &serde_json::value::RawValue = serde_json::from_str(value)
                    .map_err(serde::ser::Error::custom)?;
                raw.serialize(serializer)
            }
            JsonKind::Array => {
                let mut seq = serializer.serialize_seq(None)?;
                for (_, child) in self.children() { seq.serialize_element(&child)?; }
                seq.end()
            }
            JsonKind::Object => {
                let mut map = serializer.serialize_map(None)?;
                for (key, child) in self.children() {
                    map.serialize_entry(key.ok_or_else(|| serde::ser::Error::custom("object key absent"))?, &child)?;
                }
                map.end()
            }
        }
    }
}

/// Explicit source policy, applied before any output field selection.
pub struct RedactedSubtree<'a> {
    root: JsonSubtree<'a>,
    node: JsonSubtree<'a>,
    paths: &'a [String],
    prefix: &'a str,
    masked: bool,
}
impl<'a> RedactedSubtree<'a> {
    pub fn new(root: JsonSubtree<'a>, paths: &'a [String], prefix: &'a str) -> Self {
        Self { root, node: root, paths, prefix, masked: false }
    }
    /// Select only from the protected view. Descendants of a secret source
    /// remain a placeholder rather than exposing raw values through selectors.
    pub fn pointer(&self, path: &str) -> Result<Option<Self>, crate::json_pointer::InvalidJsonPointer> {
        let pointer = crate::json_pointer::JsonPointer::parse(path)?;
        let is_secret = |node: JsonSubtree<'a>| self.paths.iter().any(|secret| {
            secret.strip_prefix(self.prefix).and_then(|path| self.root.pointer(path).ok().flatten())
                .is_some_and(|target| target.same_node(node))
        });
        let mut node = self.node;
        let mut masked = self.masked || is_secret(node);
        for segment in pointer.segments() {
            let next = match node.kind() {
                JsonKind::Object => node.children().find(|(key, _)| key.is_some_and(|key| segment.matches(key))).map(|(_, child)| child),
                JsonKind::Array => segment.array_index().and_then(|index| node.children().nth(index)).map(|(_, child)| child),
                _ => None,
            };
            let Some(next) = next else { return Ok(None); };
            node = next;
            masked |= is_secret(node);
        }
        Ok(Some(Self { node, masked, ..*self }))
    }
    fn child(&self, node: JsonSubtree<'a>) -> Self { Self { node, ..*self } }
}
impl Serialize for RedactedSubtree<'_> {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        let secret = self.paths.iter().any(|path| {
            path.strip_prefix(self.prefix).and_then(|path| self.root.pointer(path).ok().flatten())
                .is_some_and(|target| target.same_node(self.node))
        });
        if self.masked || secret { return s.serialize_str("<redacted>"); }
        match self.node.kind() {
            JsonKind::Object => {
                let mut map = s.serialize_map(None)?;
                for (key, child) in self.node.children() {
                    map.serialize_entry(key.ok_or_else(|| serde::ser::Error::custom("object key absent"))?, &self.child(child))?;
                }
                map.end()
            }
            JsonKind::Array => {
                let mut seq = s.serialize_seq(None)?;
                for (_, child) in self.node.children() { seq.serialize_element(&self.child(child))?; }
                seq.end()
            }
            _ => self.node.serialize(s),
        }
    }
}