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