Skip to main content

jsonschema_value/
lib.rs

1//! JSON value representations and semantics shared by the validator and its bindings.
2
3pub mod cmp;
4#[cfg(feature = "conformance")]
5pub mod conformance;
6pub mod numeric;
7// The bound checks take a `serde_json::Number`, which only that feature makes a `JsonNumber`.
8#[cfg(feature = "serde_json")]
9pub mod numeric_check;
10pub mod types;
11pub mod unique;
12
13#[cfg(feature = "magnus")]
14mod magnus;
15#[cfg(feature = "pyo3")]
16mod pyo3;
17#[cfg(feature = "serde_json")]
18mod serde_json;
19
20#[cfg(feature = "magnus")]
21pub use magnus::{
22    child as magnus_child, invalidate_members_cache as magnus_invalidate_members_cache,
23    is_object as magnus_is_object, probe_root as magnus_probe_root,
24    take_pending_error as magnus_take_pending_error, Magnus, PendingError,
25    PendingErrorScope as MagnusPendingErrorScope, RbNode,
26};
27#[cfg(feature = "pyo3")]
28pub use pyo3::{probe_root, take_pending_error, PendingErrorScope, Pyo3};
29#[cfg(feature = "serde_json")]
30pub use serde_json::SerdeJson;
31
32use std::{borrow::Cow, fmt, sync::OnceLock};
33
34use ::serde_json::Value;
35
36use crate::types::JsonType;
37
38/// The instance a validation error reports, built once and cached.
39pub enum LazyInstance<'a> {
40    Ready(Cow<'a, Value>),
41    /// Built on first read. A `fn` pointer rather than a boxed closure: dropck cannot see through
42    /// a `dyn` bounded by `'a` and would demand borrows outlive the error's drop, not just its use.
43    Deferred {
44        bytes: &'a [u8],
45        tag: u32,
46        // Elided, so `for<'r> fn(&'r [u8], u32)`: a lifetime in argument position is contravariant
47        // and would fight `bytes`' covariance, making the enum invariant in `'a`.
48        make: fn(&[u8], u32) -> Value,
49        // `'static`, not `'a`: `OnceLock` is invariant in its parameter, which would otherwise
50        // infect every lifetime this type appears under, `ValidationError<'a>` included.
51        cell: OnceLock<Cow<'static, Value>>,
52    },
53}
54
55impl<'a> LazyInstance<'a> {
56    /// The instance, building and caching it on the first call.
57    pub fn get(&self) -> &Cow<'a, Value> {
58        match self {
59            LazyInstance::Ready(value) => value,
60            LazyInstance::Deferred {
61                bytes,
62                tag,
63                make,
64                cell,
65            } => cell.get_or_init(|| Cow::Owned(make(bytes, *tag))),
66        }
67    }
68
69    /// Consumes `self`, returning the instance without cloning an already-built one.
70    #[must_use]
71    pub fn into_cow(self) -> Cow<'a, Value> {
72        match self {
73            LazyInstance::Ready(value) => value,
74            LazyInstance::Deferred {
75                bytes,
76                tag,
77                make,
78                cell,
79            } => cell
80                .into_inner()
81                .unwrap_or_else(|| Cow::Owned(make(bytes, tag))),
82        }
83    }
84}
85
86impl fmt::Debug for LazyInstance<'_> {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        fmt::Debug::fmt(self.get(), f)
89    }
90}
91
92/// One JSON representation.
93pub trait Json: Sized + Send + Sync + 'static {
94    type Node<'a>: Node<'a, Self>;
95
96    /// Property name prepared once at compile time, for repeated object lookups.
97    type PreparedKey: Send + Sync;
98
99    /// Scratch storage for [`Json::with_string_node`], reusable across calls.
100    type StringBuffer: Default;
101
102    fn prepare_key(key: &str) -> Self::PreparedKey;
103
104    /// Call `f` with a node holding `string`, backed by `buffer`.
105    ///
106    /// `propertyNames` validates each property name through this, so names run through the
107    /// same subschema machinery as any other node of the representation.
108    ///
109    /// Representations whose nodes point into an encoded document have two options: a plain
110    /// string variant on the node type, or encoding a single-string document into `buffer`.
111    fn with_string_node<T>(
112        buffer: &mut Self::StringBuffer,
113        string: &str,
114        f: impl FnOnce(Self::Node<'_>) -> T,
115    ) -> T;
116}
117
118/// What tells one node from another within a validation call.
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub struct NodeIdentity {
121    address: usize,
122    tag: u32,
123}
124
125impl NodeIdentity {
126    /// For representations where a live node's address is its own.
127    #[must_use]
128    pub fn new(address: usize) -> Self {
129        Self { address, tag: 0 }
130    }
131
132    /// For representations where nodes share an address, such as an arena addressed by index.
133    #[must_use]
134    pub fn tagged(address: usize, tag: u32) -> Self {
135        Self { address, tag }
136    }
137}
138
139/// A JSON number, readable without constructing a [`::serde_json::Number`].
140pub trait JsonNumber {
141    fn as_u64(&self) -> Option<u64>;
142    fn as_i64(&self) -> Option<i64>;
143    fn as_f64(&self) -> Option<f64>;
144
145    /// Decimal digits; the only form that holds values outside the primitives.
146    fn as_str(&self) -> Cow<'_, str>;
147
148    /// For cold paths: error construction and annotations.
149    fn to_number(&self) -> Cow<'_, ::serde_json::Number>;
150
151    /// `type: integer` checks call this per number: override it where the default's
152    /// [`JsonNumber::to_number`] round-trip is not free (e.g. decimal representations).
153    fn is_integer(&self) -> bool {
154        crate::types::number_is_integer(&self.to_number())
155    }
156}
157
158/// One JSON value; `Clone` must be cheap.
159pub trait Node<'a, F: Json>: Clone {
160    type Object: Object<'a, F, Node = Self>;
161    type Array: Array<'a, F, Node = Self>;
162    type Number: JsonNumber;
163
164    fn as_object(&self) -> Option<Self::Object>;
165    fn as_array(&self) -> Option<Self::Array>;
166    fn as_string(&self) -> Option<Cow<'a, str>>;
167
168    fn as_number(&self) -> Option<Self::Number>;
169    fn as_boolean(&self) -> Option<bool>;
170    fn is_null(&self) -> bool;
171
172    /// Must agree with `as_number().is_some()`; override where `as_number` has to construct.
173    fn is_number(&self) -> bool {
174        self.as_number().is_some()
175    }
176
177    fn is_string(&self) -> bool {
178        self.json_type() == JsonType::String
179    }
180
181    /// Numbers always report [`JsonType::Number`]; integer-ness is a numeric property, not a type.
182    fn json_type(&self) -> JsonType;
183
184    /// Length in Unicode code points.
185    fn string_length(&self) -> Option<u64> {
186        self.as_string().map(|string| string.chars().count() as u64)
187    }
188
189    /// Equality against a `const`/`enum` value; numbers compare mathematically.
190    fn equals_value(&self, expected: &Value) -> bool {
191        crate::cmp::equal(&self.to_value(), expected)
192    }
193
194    /// For cold paths only: error construction, annotations, the `equals_value` and
195    /// `is_unique` defaults (`const`/`enum`/`uniqueItems`), and serde-only custom keywords.
196    fn to_value(&self) -> Cow<'a, Value>;
197
198    /// The instance a validation error reports. Defaults to eager [`Node::to_value`]; override only
199    /// where the node is `Send + Sync` without a VM lock — `Magnus` would compile but be unsound.
200    fn lazy_value(&self) -> LazyInstance<'a> {
201        LazyInstance::Ready(self.to_value())
202    }
203
204    /// Identity for `$ref` cycle detection and `is_valid` memoization.
205    ///
206    /// Nodes alive at once must never share one, and two handles on a node must report the same
207    /// one, or a collision reports a cycle that is not there. A container's must never pass to a
208    /// later node: [`Node::container_identity`] keys a cache outliving it. `None` opts out,
209    /// leaving recursion bounded only by the stack.
210    fn identity(&self) -> Option<NodeIdentity>;
211
212    fn container_identity(&self) -> Option<NodeIdentity> {
213        if matches!(self.json_type(), JsonType::Object | JsonType::Array) {
214            self.identity()
215        } else {
216            None
217        }
218    }
219}
220
221pub trait Object<'a, F: Json> {
222    type Node: Node<'a, F>;
223    type MemberName: AsRef<str> + Into<Cow<'a, str>>;
224    type MembersIter: Iterator<Item = (Self::MemberName, Self::Node)>;
225
226    fn len(&self) -> usize;
227    fn is_empty(&self) -> bool {
228        self.len() == 0
229    }
230    fn get(&self, key: &F::PreparedKey) -> Option<Self::Node>;
231    fn members(&self) -> Self::MembersIter;
232}
233
234// `len` bounds validation; no caller probes emptiness.
235#[allow(clippy::len_without_is_empty)]
236pub trait Array<'a, F: Json> {
237    type Node: Node<'a, F>;
238    type ElementsIter: Iterator<Item = Self::Node>;
239
240    fn len(&self) -> usize;
241    fn elements(&self) -> Self::ElementsIter;
242
243    /// `uniqueItems`: every element distinct under JSON equality.
244    fn is_unique(&self) -> bool {
245        let values: Vec<Cow<'a, Value>> =
246            self.elements().map(|element| element.to_value()).collect();
247        crate::unique::is_unique(&values)
248    }
249}