Skip to main content

selene_core/
json_value.rs

1use std::{collections::BTreeSet, fmt, sync::Arc};
2
3use serde::{
4    Deserialize, Deserializer, Serialize, Serializer,
5    de::{DeserializeSeed, Error as DeError, MapAccess, SeqAccess, Visitor},
6};
7use serde_json::{Map as SerdeJsonMap, Value as SerdeJsonValue};
8
9use crate::{
10    CoreError, CoreResult, DbString, db_string::MAX_DB_STRING_BYTES, json_patch::apply_json_patch,
11};
12
13/// Selector used by JSON path-existence helpers.
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub enum JsonPathSelector {
16    /// Select an object member by key.
17    Key(DbString),
18    /// Select an array element by signed index.
19    ///
20    /// Negative indexes count from the end, matching `json_has_path`.
21    Index(i64),
22    /// Select an array element by unsigned index.
23    UnsignedIndex(u64),
24}
25
26/// Native JSON payload stored as a first-class [`crate::Value`].
27///
28/// `JsonValue` validates once at construction and deserialization so every
29/// engine layer can assume a finite JSON data-model value whose strings and
30/// container cardinalities stay inside the implementation-defined GQL caps.
31#[derive(Clone, Debug, PartialEq)]
32pub struct JsonValue {
33    value: Arc<SerdeJsonValue>,
34}
35
36/// Borrowed JSON subvalue selected from a validated [`JsonValue`].
37///
38/// This keeps scan paths from cloning selected JSON values until the caller
39/// knows the value must be materialized into an owned result.
40#[derive(Clone, Copy, Debug, PartialEq)]
41pub struct JsonValueRef<'a> {
42    value: &'a SerdeJsonValue,
43}
44
45impl<'a> JsonValueRef<'a> {
46    /// Borrow the underlying serde-json value.
47    #[must_use]
48    pub fn as_serde(self) -> &'a SerdeJsonValue {
49        self.value
50    }
51
52    /// Clone this validated subvalue into an owned [`JsonValue`].
53    #[must_use]
54    pub fn to_owned_json_value(self) -> JsonValue {
55        JsonValue {
56            value: Arc::new(self.value.clone()),
57        }
58    }
59}
60
61impl JsonValue {
62    /// Build a validated JSON value from an owned serde-json value.
63    pub fn new(value: SerdeJsonValue) -> CoreResult<Self> {
64        validate_json_value(&value)?;
65        Ok(Self {
66            value: Arc::new(value),
67        })
68    }
69
70    /// Parse and validate a JSON value from text.
71    ///
72    /// # Errors
73    ///
74    /// Returns [`CoreError::JsonParse`] when `text` is not valid JSON, or the
75    /// usual value-limit errors when the parsed value exceeds engine caps.
76    pub fn parse_str(text: &str) -> CoreResult<Self> {
77        let value = parse_json_text(text).map_err(|err| CoreError::JsonParse {
78            message: err.to_string(),
79        })?;
80        Self::new(value)
81    }
82
83    /// Borrow the underlying serde-json value.
84    #[must_use]
85    pub fn as_serde(&self) -> &SerdeJsonValue {
86        &self.value
87    }
88
89    /// Clone the shared JSON storage.
90    #[must_use]
91    pub fn as_arc(&self) -> Arc<SerdeJsonValue> {
92        Arc::clone(&self.value)
93    }
94
95    /// Return a stable compact JSON rendering with object keys sorted.
96    #[must_use]
97    pub fn to_canonical_string(&self) -> String {
98        let mut output = String::new();
99        write_json_canonical(self.as_serde(), &mut output);
100        output
101    }
102
103    /// Return the JSON data-model type name.
104    #[must_use]
105    pub fn json_type_name(&self) -> &'static str {
106        match self.as_serde() {
107            SerdeJsonValue::Null => "null",
108            SerdeJsonValue::Bool(_) => "boolean",
109            SerdeJsonValue::Number(_) => "number",
110            SerdeJsonValue::String(_) => "string",
111            SerdeJsonValue::Array(_) => "array",
112            SerdeJsonValue::Object(_) => "object",
113        }
114    }
115
116    /// Return true when this JSON value recursively contains `candidate`.
117    ///
118    /// Objects contain candidates whose keys are present and whose values are
119    /// themselves contained. Arrays contain array candidates when each
120    /// candidate element is contained by at least one target element. A scalar
121    /// or object candidate also matches any containing target array element.
122    #[must_use]
123    pub fn contains(&self, candidate: &Self) -> bool {
124        json_contains_value(self.as_serde(), candidate.as_serde())
125    }
126
127    /// Return true when every selector in `path` resolves inside this value.
128    ///
129    /// Stored-value shape mismatches return false. This makes the helper useful
130    /// for heterogeneous graph scans where one malformed document should not
131    /// abort the entire candidate search.
132    #[must_use]
133    pub fn path_exists(&self, path: &[JsonPathSelector]) -> bool {
134        select_json_path(self.as_serde(), path).is_some()
135    }
136
137    /// Return the JSON subvalue selected by `path`.
138    ///
139    /// Stored-value shape mismatches return [`None`]. A selected JSON `null`
140    /// still returns `Some(JsonValue)`, which lets callers distinguish present
141    /// null values from absent paths.
142    #[must_use]
143    pub fn path_value(&self, path: &[JsonPathSelector]) -> Option<Self> {
144        self.path_value_ref(path)
145            .map(JsonValueRef::to_owned_json_value)
146    }
147
148    /// Borrow the JSON subvalue selected by `path`.
149    ///
150    /// Stored-value shape mismatches return [`None`]. A selected JSON `null`
151    /// still returns `Some(JsonValueRef)`, which lets callers distinguish
152    /// present null values from absent paths without cloning the subvalue.
153    #[must_use]
154    pub fn path_value_ref(&self, path: &[JsonPathSelector]) -> Option<JsonValueRef<'_>> {
155        select_json_path(self.as_serde(), path).map(|value| JsonValueRef { value })
156    }
157
158    /// Return true when `path` selects a JSON value that contains `candidate`.
159    ///
160    /// Stored-value shape mismatches return false, matching
161    /// [`Self::path_exists`]. Containment follows [`Self::contains`] semantics
162    /// on the selected subvalue.
163    #[must_use]
164    pub fn path_contains(&self, path: &[JsonPathSelector], candidate: &Self) -> bool {
165        select_json_path(self.as_serde(), path)
166            .is_some_and(|value| json_contains_value(value, candidate.as_serde()))
167    }
168
169    /// Return the result of applying an RFC 7396 JSON Merge Patch document.
170    ///
171    /// The operation is copy-on-write: this value and `patch` are left
172    /// unchanged, and the merged result is validated as a fresh [`JsonValue`].
173    ///
174    /// # Errors
175    ///
176    /// Returns the usual value-limit errors if the merged result exceeds engine
177    /// caps.
178    pub fn merge_patch(&self, patch: &Self) -> CoreResult<Self> {
179        let mut target = self.as_serde().clone();
180        merge_patch_value(&mut target, patch.as_serde());
181        Self::new(target)
182    }
183
184    /// Return the result of applying an RFC 6902 JSON Patch document.
185    ///
186    /// The operation is atomic from the caller's perspective: this value and
187    /// `patch` are left unchanged, and an invalid patch returns an error
188    /// without exposing any intermediate document state.
189    ///
190    /// # Errors
191    ///
192    /// Returns [`CoreError::JsonPatch`] when the patch document is malformed or
193    /// an operation fails. Returns the usual value-limit errors if the patched
194    /// result exceeds engine caps.
195    pub fn apply_patch(&self, patch: &Self) -> CoreResult<Self> {
196        Self::new(apply_json_patch(self.as_serde(), patch.as_serde())?)
197    }
198}
199
200fn select_json_path<'a>(
201    mut current: &'a SerdeJsonValue,
202    path: &[JsonPathSelector],
203) -> Option<&'a SerdeJsonValue> {
204    for selector in path {
205        current = select_json_child(current, selector)?;
206    }
207    Some(current)
208}
209
210fn select_json_child<'a>(
211    current: &'a SerdeJsonValue,
212    selector: &JsonPathSelector,
213) -> Option<&'a SerdeJsonValue> {
214    match (current, selector) {
215        (SerdeJsonValue::Object(values), JsonPathSelector::Key(key)) => values.get(key.as_str()),
216        (SerdeJsonValue::Array(values), JsonPathSelector::Index(index)) => {
217            signed_array_index(*index, values.len()).and_then(|index| values.get(index))
218        }
219        (SerdeJsonValue::Array(values), JsonPathSelector::UnsignedIndex(index)) => {
220            usize::try_from(*index)
221                .ok()
222                .and_then(|index| values.get(index))
223        }
224        _ => None,
225    }
226}
227
228fn signed_array_index(index: i64, len: usize) -> Option<usize> {
229    if index >= 0 {
230        usize::try_from(index).ok().filter(|index| *index < len)
231    } else {
232        let offset = usize::try_from(index.unsigned_abs()).ok()?;
233        (offset <= len).then_some(len - offset)
234    }
235}
236
237impl TryFrom<SerdeJsonValue> for JsonValue {
238    type Error = CoreError;
239
240    fn try_from(value: SerdeJsonValue) -> Result<Self, Self::Error> {
241        Self::new(value)
242    }
243}
244
245impl From<JsonValue> for SerdeJsonValue {
246    fn from(value: JsonValue) -> Self {
247        value.as_serde().clone()
248    }
249}
250
251impl Serialize for JsonValue {
252    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
253    where
254        S: Serializer,
255    {
256        if serializer.is_human_readable() {
257            self.as_serde().serialize(serializer)
258        } else {
259            serializer.serialize_str(&self.to_canonical_string())
260        }
261    }
262}
263
264impl<'de> Deserialize<'de> for JsonValue {
265    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
266    where
267        D: Deserializer<'de>,
268    {
269        let value = if deserializer.is_human_readable() {
270            StrictJsonValueSeed.deserialize(deserializer)?
271        } else {
272            let value = String::deserialize(deserializer)?;
273            parse_json_text(&value).map_err(serde::de::Error::custom)?
274        };
275        Self::new(value).map_err(serde::de::Error::custom)
276    }
277}
278
279fn parse_json_text(text: &str) -> Result<SerdeJsonValue, serde_json::Error> {
280    let mut deserializer = serde_json::Deserializer::from_str(text);
281    let value = StrictJsonValueSeed.deserialize(&mut deserializer)?;
282    deserializer.end()?;
283    Ok(value)
284}
285
286struct StrictJsonValueSeed;
287
288impl<'de> DeserializeSeed<'de> for StrictJsonValueSeed {
289    type Value = SerdeJsonValue;
290
291    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
292    where
293        D: Deserializer<'de>,
294    {
295        deserializer.deserialize_any(StrictJsonValueVisitor)
296    }
297}
298
299struct StrictJsonValueVisitor;
300
301impl<'de> Visitor<'de> for StrictJsonValueVisitor {
302    type Value = SerdeJsonValue;
303
304    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
305        formatter.write_str("a JSON value with unique object keys")
306    }
307
308    fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
309        Ok(SerdeJsonValue::Bool(value))
310    }
311
312    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
313        Ok(SerdeJsonValue::Number(value.into()))
314    }
315
316    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
317        Ok(SerdeJsonValue::Number(value.into()))
318    }
319
320    fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
321    where
322        E: DeError,
323    {
324        let number = serde_json::Number::from_f64(value)
325            .ok_or_else(|| E::custom("JSON number is not finite"))?;
326        Ok(SerdeJsonValue::Number(number))
327    }
328
329    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
330        Ok(SerdeJsonValue::String(value.to_owned()))
331    }
332
333    fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
334        Ok(SerdeJsonValue::String(value))
335    }
336
337    fn visit_none<E>(self) -> Result<Self::Value, E> {
338        Ok(SerdeJsonValue::Null)
339    }
340
341    fn visit_unit<E>(self) -> Result<Self::Value, E> {
342        Ok(SerdeJsonValue::Null)
343    }
344
345    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
346    where
347        D: Deserializer<'de>,
348    {
349        StrictJsonValueSeed.deserialize(deserializer)
350    }
351
352    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
353    where
354        A: SeqAccess<'de>,
355    {
356        let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0));
357        while let Some(value) = seq.next_element_seed(StrictJsonValueSeed)? {
358            values.push(value);
359        }
360        Ok(SerdeJsonValue::Array(values))
361    }
362
363    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
364    where
365        A: MapAccess<'de>,
366    {
367        let mut seen = BTreeSet::new();
368        let mut values = SerdeJsonMap::new();
369        while let Some(key) = map.next_key::<String>()? {
370            if !seen.insert(key.clone()) {
371                return Err(A::Error::custom(format!(
372                    "duplicate JSON object key '{key}'"
373                )));
374            }
375            let value = map.next_value_seed(StrictJsonValueSeed)?;
376            values.insert(key, value);
377        }
378        Ok(SerdeJsonValue::Object(values))
379    }
380}
381
382fn validate_json_value(value: &SerdeJsonValue) -> CoreResult<()> {
383    match value {
384        SerdeJsonValue::Null | SerdeJsonValue::Bool(_) | SerdeJsonValue::Number(_) => Ok(()),
385        SerdeJsonValue::String(value) => {
386            validate_json_string_len(value.len())?;
387            Ok(())
388        }
389        SerdeJsonValue::Array(values) => {
390            ensure_json_container_len(values.len())?;
391            for value in values {
392                validate_json_value(value)?;
393            }
394            Ok(())
395        }
396        SerdeJsonValue::Object(values) => {
397            ensure_json_container_len(values.len())?;
398            for (key, value) in values {
399                validate_json_string_len(key.len())?;
400                validate_json_value(value)?;
401            }
402            Ok(())
403        }
404    }
405}
406
407fn validate_json_string_len(len: usize) -> CoreResult<()> {
408    if len > MAX_DB_STRING_BYTES {
409        return Err(CoreError::StringTooLong {
410            got: len,
411            max: u32::MAX,
412        });
413    }
414    Ok(())
415}
416
417fn ensure_json_container_len(len: usize) -> CoreResult<()> {
418    if len > u32::MAX as usize {
419        return Err(CoreError::ConstructedValueTooLarge {
420            got: len,
421            max: u32::MAX,
422        });
423    }
424    Ok(())
425}
426
427fn json_contains_value(target: &SerdeJsonValue, candidate: &SerdeJsonValue) -> bool {
428    match (target, candidate) {
429        (SerdeJsonValue::Object(target), SerdeJsonValue::Object(candidate)) => {
430            candidate.iter().all(|(key, value)| {
431                target
432                    .get(key)
433                    .is_some_and(|found| json_contains_value(found, value))
434            })
435        }
436        (SerdeJsonValue::Array(target), SerdeJsonValue::Array(candidate)) => candidate
437            .iter()
438            .all(|value| target.iter().any(|found| json_contains_value(found, value))),
439        (SerdeJsonValue::Array(target), candidate) => target
440            .iter()
441            .any(|found| json_contains_value(found, candidate)),
442        _ => target == candidate,
443    }
444}
445
446fn merge_patch_value(target: &mut SerdeJsonValue, patch: &SerdeJsonValue) {
447    let SerdeJsonValue::Object(patch) = patch else {
448        *target = patch.clone();
449        return;
450    };
451
452    if !target.is_object() {
453        *target = SerdeJsonValue::Object(SerdeJsonMap::new());
454    }
455    let target = target
456        .as_object_mut()
457        .expect("target was normalized to object");
458
459    for (key, value) in patch {
460        if value.is_null() {
461            target.remove(key);
462        } else {
463            let entry = target.entry(key.clone()).or_insert(SerdeJsonValue::Null);
464            merge_patch_value(entry, value);
465        }
466    }
467}
468
469fn write_json_canonical(value: &SerdeJsonValue, output: &mut String) {
470    match value {
471        SerdeJsonValue::Null => output.push_str("null"),
472        SerdeJsonValue::Bool(value) => output.push_str(if *value { "true" } else { "false" }),
473        SerdeJsonValue::Number(value) => output.push_str(&value.to_string()),
474        SerdeJsonValue::String(value) => {
475            output.push_str(&serde_json::to_string(value).expect("JSON string rendering succeeds"));
476        }
477        SerdeJsonValue::Array(values) => {
478            output.push('[');
479            for (index, value) in values.iter().enumerate() {
480                if index > 0 {
481                    output.push(',');
482                }
483                write_json_canonical(value, output);
484            }
485            output.push(']');
486        }
487        SerdeJsonValue::Object(values) => {
488            output.push('{');
489            let mut entries = values.iter().collect::<Vec<_>>();
490            entries.sort_unstable_by(|lhs, rhs| lhs.0.cmp(rhs.0));
491            for (index, (key, value)) in entries.into_iter().enumerate() {
492                if index > 0 {
493                    output.push(',');
494                }
495                output.push_str(&serde_json::to_string(key).expect("JSON key rendering succeeds"));
496                output.push(':');
497                write_json_canonical(value, output);
498            }
499            output.push('}');
500        }
501    }
502}
503
504#[cfg(test)]
505mod tests;