Skip to main content

pjson_rs_domain/value_objects/
json_data.rs

1//! Domain-specific JSON data value object
2//!
3//! Provides a Clean Architecture compliant representation of JSON data
4//! without depending on external serialization libraries in the domain layer.
5
6use crate::{DomainError, DomainResult};
7use serde::de::{
8    Deserialize, DeserializeSeed, Deserializer, Error as DeError, MapAccess, SeqAccess, Visitor,
9};
10use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
11use std::collections::HashMap;
12use std::fmt;
13
14/// Domain-specific representation of JSON-like data
15/// This replaces serde_json::Value to maintain Clean Architecture principles
16#[derive(Debug, Clone, PartialEq, Default)]
17#[non_exhaustive]
18pub enum JsonData {
19    #[default]
20    /// Null value
21    Null,
22    /// Boolean value
23    Bool(bool),
24    /// Integer value
25    Integer(i64),
26    /// Float value (stored as f64 for simplicity)
27    Float(f64),
28    /// String value
29    String(String),
30    /// Array of JsonData values
31    Array(Vec<JsonData>),
32    /// Object with string keys and JsonData values
33    Object(HashMap<String, JsonData>),
34}
35
36impl JsonData {
37    /// Create a new null value
38    pub fn null() -> Self {
39        Self::Null
40    }
41
42    /// Create a new boolean value
43    pub fn bool(value: bool) -> Self {
44        Self::Bool(value)
45    }
46
47    /// Create a new integer value
48    pub fn integer(value: i64) -> Self {
49        Self::Integer(value)
50    }
51
52    /// Create a new float value.
53    ///
54    /// Returns `Err` when `value` is NaN or infinite. JSON (RFC 8259 §6) does not
55    /// allow non-finite numbers, so a `JsonData` containing one could never be
56    /// serialized to valid JSON.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use pjson_rs_domain::value_objects::JsonData;
62    ///
63    /// assert!(JsonData::float(3.14).is_ok());
64    /// assert!(JsonData::float(f64::NAN).is_err());
65    /// assert!(JsonData::float(f64::INFINITY).is_err());
66    /// ```
67    pub fn float(value: f64) -> DomainResult<Self> {
68        if value.is_nan() || value.is_infinite() {
69            return Err(DomainError::InvalidInput(
70                "JSON does not support NaN or infinite float values (RFC 8259 §6)".to_string(),
71            ));
72        }
73        Ok(Self::Float(value))
74    }
75
76    /// Create a new string value
77    pub fn string<S: Into<String>>(value: S) -> Self {
78        Self::String(value.into())
79    }
80
81    /// Create a new array value
82    pub fn array(values: Vec<JsonData>) -> Self {
83        Self::Array(values)
84    }
85
86    /// Create a new object value
87    pub fn object(values: HashMap<String, JsonData>) -> Self {
88        Self::Object(values)
89    }
90
91    /// Check if value is null
92    pub fn is_null(&self) -> bool {
93        matches!(self, Self::Null)
94    }
95
96    /// Check if value is boolean
97    pub fn is_bool(&self) -> bool {
98        matches!(self, Self::Bool(_))
99    }
100
101    /// Check if value is integer
102    pub fn is_integer(&self) -> bool {
103        matches!(self, Self::Integer(_))
104    }
105
106    /// Check if value is float
107    pub fn is_float(&self) -> bool {
108        matches!(self, Self::Float(_))
109    }
110
111    /// Check if value is number (integer or float)
112    pub fn is_number(&self) -> bool {
113        matches!(self, Self::Integer(_) | Self::Float(_))
114    }
115
116    /// Check if value is string
117    pub fn is_string(&self) -> bool {
118        matches!(self, Self::String(_))
119    }
120
121    /// Check if value is array
122    pub fn is_array(&self) -> bool {
123        matches!(self, Self::Array(_))
124    }
125
126    /// Check if value is object
127    pub fn is_object(&self) -> bool {
128        matches!(self, Self::Object(_))
129    }
130
131    /// Get boolean value if this is a boolean
132    pub fn as_bool(&self) -> Option<bool> {
133        match self {
134            Self::Bool(b) => Some(*b),
135            _ => None,
136        }
137    }
138
139    /// Get integer value if this is an integer
140    pub fn as_i64(&self) -> Option<i64> {
141        match self {
142            Self::Integer(i) => Some(*i),
143            _ => None,
144        }
145    }
146
147    /// Get float value if this is a float
148    pub fn as_f64(&self) -> Option<f64> {
149        match self {
150            Self::Float(f) => Some(*f),
151            Self::Integer(i) => Some(*i as f64),
152            _ => None,
153        }
154    }
155
156    /// Get string value if this is a string
157    pub fn as_str(&self) -> Option<&str> {
158        match self {
159            Self::String(s) => Some(s),
160            _ => None,
161        }
162    }
163
164    /// Get array value if this is an array
165    pub fn as_array(&self) -> Option<&Vec<JsonData>> {
166        match self {
167            Self::Array(arr) => Some(arr),
168            _ => None,
169        }
170    }
171
172    /// Get mutable array value if this is an array
173    pub fn as_array_mut(&mut self) -> Option<&mut Vec<JsonData>> {
174        match self {
175            Self::Array(arr) => Some(arr),
176            _ => None,
177        }
178    }
179
180    /// Get object value if this is an object
181    pub fn as_object(&self) -> Option<&HashMap<String, JsonData>> {
182        match self {
183            Self::Object(obj) => Some(obj),
184            _ => None,
185        }
186    }
187
188    /// Get mutable object value if this is an object
189    pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, JsonData>> {
190        match self {
191            Self::Object(obj) => Some(obj),
192            _ => None,
193        }
194    }
195
196    /// Get value by key (if this is an object)
197    pub fn get(&self, key: &str) -> Option<&JsonData> {
198        match self {
199            Self::Object(obj) => obj.get(key),
200            _ => None,
201        }
202    }
203
204    /// Get nested value by path (dot notation)
205    pub fn path(&self, path: &str) -> Option<&JsonData> {
206        let parts: Vec<&str> = path.split('.').collect();
207        let mut current = self;
208
209        for part in parts {
210            match current {
211                Self::Object(obj) => {
212                    current = obj.get(part)?;
213                }
214                _ => return None,
215            }
216        }
217
218        Some(current)
219    }
220
221    /// Set nested value by path (dot notation)
222    pub fn set_path(&mut self, path: &str, value: JsonData) -> bool {
223        let parts: Vec<&str> = path.split('.').collect();
224        if parts.is_empty() {
225            return false;
226        }
227
228        if parts.len() == 1 {
229            if let Self::Object(obj) = self {
230                obj.insert(parts[0].to_string(), value);
231                return true;
232            }
233            return false;
234        }
235
236        // Navigate to parent and create intermediate objects if needed
237        let mut current = self;
238        for part in &parts[..parts.len() - 1] {
239            match current {
240                Self::Object(obj) => {
241                    if !obj.contains_key(*part) {
242                        obj.insert(part.to_string(), Self::object(HashMap::new()));
243                    }
244                    current = obj
245                        .get_mut(*part)
246                        .expect("Key must exist as we just inserted it above");
247                }
248                _ => return false,
249            }
250        }
251
252        // Set final value
253        if let Self::Object(obj) = current {
254            obj.insert(parts[parts.len() - 1].to_string(), value);
255            true
256        } else {
257            false
258        }
259    }
260
261    /// Estimate memory size in bytes
262    pub fn memory_size(&self) -> usize {
263        match self {
264            Self::Null => 1,
265            Self::Bool(_) => 1,
266            Self::Integer(_) => 8,
267            Self::Float(_) => 8,
268            Self::String(s) => s.len() * 2, // UTF-16 estimation
269            Self::Array(arr) => 8 + arr.iter().map(|v| v.memory_size()).sum::<usize>(),
270            Self::Object(obj) => {
271                16 + obj
272                    .iter()
273                    .map(|(k, v)| k.len() * 2 + v.memory_size())
274                    .sum::<usize>()
275            }
276        }
277    }
278}
279
280impl fmt::Display for JsonData {
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        match self {
283            Self::Null => write!(f, "null"),
284            Self::Bool(b) => write!(f, "{b}"),
285            Self::Integer(i) => write!(f, "{i}"),
286            Self::Float(float_val) => write!(f, "{float_val}"),
287            Self::String(s) => write!(f, "\"{s}\""),
288            Self::Array(arr) => {
289                write!(f, "[")?;
290                for (i, item) in arr.iter().enumerate() {
291                    if i > 0 {
292                        write!(f, ",")?;
293                    }
294                    write!(f, "{item}")?;
295                }
296                write!(f, "]")
297            }
298            Self::Object(obj) => {
299                write!(f, "{{")?;
300                for (i, (key, value)) in obj.iter().enumerate() {
301                    if i > 0 {
302                        write!(f, ",")?;
303                    }
304                    write!(f, "\"{key}\":{value}")?;
305                }
306                write!(f, "}}")
307            }
308        }
309    }
310}
311
312impl Eq for JsonData {}
313
314impl std::hash::Hash for JsonData {
315    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
316        match self {
317            Self::Null => 0u8.hash(state),
318            Self::Bool(b) => {
319                1u8.hash(state);
320                b.hash(state);
321            }
322            Self::Integer(i) => {
323                2u8.hash(state);
324                i.hash(state);
325            }
326            Self::Float(f) => {
327                3u8.hash(state);
328                // For floats, convert to bits for consistent hashing
329                f.to_bits().hash(state);
330            }
331            Self::String(s) => {
332                4u8.hash(state);
333                s.hash(state);
334            }
335            Self::Array(arr) => {
336                5u8.hash(state);
337                arr.hash(state);
338            }
339            Self::Object(obj) => {
340                6u8.hash(state);
341                // HashMap doesn't have deterministic iteration order,
342                // so we need to sort keys for consistent hashing
343                let mut pairs: Vec<_> = obj.iter().collect();
344                pairs.sort_by_key(|(k, _)| *k);
345                pairs.hash(state);
346            }
347        }
348    }
349}
350
351impl From<bool> for JsonData {
352    fn from(value: bool) -> Self {
353        Self::Bool(value)
354    }
355}
356
357impl From<i64> for JsonData {
358    fn from(value: i64) -> Self {
359        Self::Integer(value)
360    }
361}
362
363impl From<String> for JsonData {
364    fn from(value: String) -> Self {
365        Self::String(value)
366    }
367}
368
369impl From<&str> for JsonData {
370    fn from(value: &str) -> Self {
371        Self::String(value.to_string())
372    }
373}
374
375impl From<Vec<JsonData>> for JsonData {
376    fn from(value: Vec<JsonData>) -> Self {
377        Self::Array(value)
378    }
379}
380
381impl From<HashMap<String, JsonData>> for JsonData {
382    fn from(value: HashMap<String, JsonData>) -> Self {
383        Self::Object(value)
384    }
385}
386
387impl From<serde_json::Value> for JsonData {
388    fn from(value: serde_json::Value) -> Self {
389        match value {
390            serde_json::Value::Null => Self::Null,
391            serde_json::Value::Bool(b) => Self::Bool(b),
392            serde_json::Value::Number(n) => {
393                if let Some(i) = n.as_i64() {
394                    Self::Integer(i)
395                } else if let Some(f) = n.as_f64() {
396                    Self::Float(f)
397                } else {
398                    Self::Float(0.0) // fallback
399                }
400            }
401            serde_json::Value::String(s) => Self::String(s),
402            serde_json::Value::Array(arr) => {
403                let converted: Vec<JsonData> = arr.into_iter().map(JsonData::from).collect();
404                Self::Array(converted)
405            }
406            serde_json::Value::Object(obj) => {
407                let converted: HashMap<String, JsonData> = obj
408                    .into_iter()
409                    .map(|(k, v)| (k, JsonData::from(v)))
410                    .collect();
411                Self::Object(converted)
412            }
413        }
414    }
415}
416
417/// Serializes [`JsonData`] as a plain JSON value rather than as a Rust enum
418/// (which would wrap every non-unit variant in a `{"VariantName": ...}`
419/// tag). This keeps the wire representation identical to what a client
420/// actually sent, and symmetric with the [`Deserialize`] impl below, which
421/// expects plain JSON on the way in.
422///
423/// # Examples
424///
425/// ```
426/// use pjson_rs_domain::value_objects::JsonData;
427/// use std::collections::HashMap;
428///
429/// let data = JsonData::object(HashMap::from([
430///     ("a".to_string(), JsonData::integer(1)),
431/// ]));
432/// assert_eq!(serde_json::to_string(&data).unwrap(), r#"{"a":1}"#);
433/// ```
434impl Serialize for JsonData {
435    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
436    where
437        S: Serializer,
438    {
439        match self {
440            JsonData::Null => serializer.serialize_unit(),
441            JsonData::Bool(b) => serializer.serialize_bool(*b),
442            JsonData::Integer(i) => serializer.serialize_i64(*i),
443            JsonData::Float(f) => serializer.serialize_f64(*f),
444            JsonData::String(s) => serializer.serialize_str(s),
445            JsonData::Array(arr) => {
446                let mut seq = serializer.serialize_seq(Some(arr.len()))?;
447                for item in arr {
448                    seq.serialize_element(item)?;
449                }
450                seq.end()
451            }
452            JsonData::Object(obj) => {
453                let mut map = serializer.serialize_map(Some(obj.len()))?;
454                for (key, value) in obj {
455                    map.serialize_entry(key, value)?;
456                }
457                map.end()
458            }
459        }
460    }
461}
462
463/// Deserializes JSON input directly into [`JsonData`], skipping the
464/// intermediate `serde_json::Value` tree that `From<serde_json::Value>`
465/// would otherwise require building and then walking a second time.
466///
467/// The implementation drives a [`Visitor`] through
468/// [`Deserializer::deserialize_any`], so it works with any self-describing
469/// format (not just `serde_json`) and constructs each [`JsonData`] variant
470/// in a single pass over the input.
471///
472/// # Examples
473///
474/// ```
475/// use pjson_rs_domain::value_objects::JsonData;
476///
477/// let data: JsonData = serde_json::from_str(r#"{"a": 1, "b": [true, null]}"#).unwrap();
478/// assert!(data.is_object());
479/// assert_eq!(data.get("a").and_then(JsonData::as_i64), Some(1));
480/// ```
481impl<'de> Deserialize<'de> for JsonData {
482    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
483    where
484        D: Deserializer<'de>,
485    {
486        deserializer.deserialize_any(JsonDataVisitor { depth: 0 })
487    }
488}
489
490/// Maximum container nesting depth accepted when deserializing [`JsonData`].
491///
492/// `JsonData::deserialize` drives a recursive [`Visitor`] through
493/// [`Deserializer::deserialize_any`], so nesting depth in the input maps
494/// one-to-one onto stack frames and onto retained per-level preallocations.
495/// Without a bound, a few bytes of nested container headers in a
496/// length-prefixed self-describing format (MessagePack, CBOR) exhaust the
497/// stack (CWE-674) and amplify retained allocation (CWE-789).
498///
499/// Set to 64, matching `pjson_rs::config::security::JsonLimits`' `max_depth`
500/// default. The constant cannot be shared, because the dependency direction
501/// is `pjs-core` -> `pjs-domain`, not the reverse; it is `pub` (re-exported at
502/// the crate root) so callers configuring their own limits can stay in sync
503/// with it rather than duplicating the value blindly. If this value changes,
504/// update it alongside the guarding tests in `pjson-rs`'s
505/// `config::security::tests` (`test_max_deserialize_depth_matches_domain_guard_defaults`,
506/// `test_jiter_config_default_max_depth_matches_domain_guard`) and `pjs-wasm`'s
507/// `security::tests::test_default_max_depth_matches_domain_guard`.
508///
509/// Together with this crate's internal per-collection preallocation cap,
510/// this bounds worst-case retained allocation for one `JsonData::deserialize`
511/// call to, order of magnitude, `MAX_DESERIALIZE_DEPTH` times that cap —
512/// every nesting level can retain up to one level's preallocation while its
513/// children are still being read, and `HashMap`'s bucket-table overhead
514/// pushes the object case somewhat above that product. This bound is per
515/// `deserialize` call, not per process: a server accepting concurrent
516/// requests must still bound concurrency or request-body size on top of it.
517pub const MAX_DESERIALIZE_DEPTH: usize = 64;
518
519/// Carries the current nesting depth into a nested [`JsonData`] value.
520///
521/// `Deserialize::deserialize` takes no state, so a nested `next_element()`
522/// would restart the visitor at depth 0. `DeserializeSeed` is serde's
523/// supported way to thread state through a recursive descent.
524struct JsonDataSeed {
525    depth: usize,
526}
527
528impl<'de> DeserializeSeed<'de> for JsonDataSeed {
529    type Value = JsonData;
530
531    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
532    where
533        D: Deserializer<'de>,
534    {
535        deserializer.deserialize_any(JsonDataVisitor { depth: self.depth })
536    }
537}
538
539/// Upper bound, in bytes of `Element`s, on the *element count* a single
540/// `size_hint()`-driven preallocation may claim before any element has
541/// actually been read from the input.
542///
543/// This bounds `capacity * size_of::<Element>()`, not the resulting
544/// allocation's actual byte size — a `HashMap::with_capacity` call fed this
545/// count can still allocate a larger backing table once load-factor and
546/// power-of-two bucket rounding are applied (e.g. ~2.65 MiB observed for a
547/// 1 MiB-worth-of-elements `HashMap<String, JsonData>` capacity), same as
548/// with `serde`'s own equivalent cap.
549///
550/// Loosely follows the cap `serde`'s own container `Deserialize` impls use
551/// via `serde::__private::size_hint::cautious` — that path is not stable
552/// public API, so an equivalent bound is reimplemented here. This
553/// implementation intentionally diverges for zero-sized `Element`s: `serde`
554/// returns 0 (no cap needed, a ZST allocates no memory regardless of count),
555/// while this returns `MAX_PREALLOC_BYTES` (harmless for the `Element`
556/// types actually used by this module's visitors, both well over one byte).
557const MAX_PREALLOC_BYTES: usize = 1024 * 1024;
558
559/// Caps a deserializer-supplied `size_hint()` so a preallocation can never
560/// claim more than [`MAX_PREALLOC_BYTES`] worth of `Element`s.
561///
562/// For length-prefixed self-describing formats (MessagePack, CBOR, etc.)
563/// `size_hint()` reflects a value read directly from untrusted input before
564/// any element is validated; using it unbounded lets a few bytes of input
565/// claim an arbitrarily large allocation (CWE-789).
566fn cautious_capacity<Element>(hint: Option<usize>) -> usize {
567    match hint {
568        Some(hint) => hint.min(MAX_PREALLOC_BYTES / size_of::<Element>().max(1)),
569        None => 0,
570    }
571}
572
573struct JsonDataVisitor {
574    depth: usize,
575}
576
577impl JsonDataVisitor {
578    /// Descends one container level, or rejects once the depth bound is hit.
579    fn enter<E: DeError>(&self) -> Result<JsonDataSeed, E> {
580        if self.depth >= MAX_DESERIALIZE_DEPTH {
581            return Err(E::custom(format_args!(
582                "JSON nesting depth exceeds maximum of {MAX_DESERIALIZE_DEPTH}"
583            )));
584        }
585        Ok(JsonDataSeed {
586            depth: self.depth + 1,
587        })
588    }
589}
590
591impl<'de> Visitor<'de> for JsonDataVisitor {
592    type Value = JsonData;
593
594    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
595        formatter.write_str("a valid JSON value")
596    }
597
598    fn visit_unit<E>(self) -> Result<Self::Value, E> {
599        Ok(JsonData::Null)
600    }
601
602    fn visit_none<E>(self) -> Result<Self::Value, E> {
603        Ok(JsonData::Null)
604    }
605
606    /// Forwards `self` unchanged, so `Option` wrapping does not consume a
607    /// depth level — correctly so, since `Some(x)` is not itself a container.
608    /// This does mean depth tracking does not cover *every* recursive call
609    /// into `deserialize_any`; it is safe only because none of `serde_json`,
610    /// `rmp-serde`, or common CBOR deserializers ever call `visit_some` from
611    /// `deserialize_any` (self-describing formats decode `Option` via
612    /// `visit_none`/direct value visits, not a wrapping `visit_some`).
613    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
614    where
615        D: Deserializer<'de>,
616    {
617        deserializer.deserialize_any(self)
618    }
619
620    fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
621        Ok(JsonData::Bool(v))
622    }
623
624    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
625        Ok(JsonData::Integer(v))
626    }
627
628    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
629        match i64::try_from(v) {
630            Ok(i) => Ok(JsonData::Integer(i)),
631            Err(_) => Ok(JsonData::Float(v as f64)),
632        }
633    }
634
635    fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
636    where
637        E: DeError,
638    {
639        if v.is_nan() || v.is_infinite() {
640            return Err(E::custom(
641                "JSON does not support NaN or infinite float values (RFC 8259 §6)",
642            ));
643        }
644        Ok(JsonData::Float(v))
645    }
646
647    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
648        Ok(JsonData::String(v.to_owned()))
649    }
650
651    fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
652        Ok(JsonData::String(v))
653    }
654
655    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
656    where
657        A: SeqAccess<'de>,
658    {
659        let seed = self.enter()?;
660        let mut vec = Vec::with_capacity(cautious_capacity::<JsonData>(seq.size_hint()));
661        while let Some(elem) = seq.next_element_seed(JsonDataSeed { depth: seed.depth })? {
662            vec.push(elem);
663        }
664        Ok(JsonData::Array(vec))
665    }
666
667    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
668    where
669        A: MapAccess<'de>,
670    {
671        let seed = self.enter()?;
672        let mut obj =
673            HashMap::with_capacity(cautious_capacity::<(String, JsonData)>(map.size_hint()));
674        // next_entry() cannot carry a seed for the value, so key and value are
675        // read separately. Keys are String, not JsonData, so they do not recurse.
676        while let Some(key) = map.next_key::<String>()? {
677            let value = map.next_value_seed(JsonDataSeed { depth: seed.depth })?;
678            obj.insert(key, value);
679        }
680        Ok(JsonData::Object(obj))
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687
688    #[test]
689    fn test_json_data_creation() {
690        assert_eq!(JsonData::null(), JsonData::Null);
691        assert_eq!(JsonData::bool(true), JsonData::Bool(true));
692        assert_eq!(JsonData::float(42.0).unwrap(), JsonData::Float(42.0));
693        assert_eq!(
694            JsonData::string("hello"),
695            JsonData::String("hello".to_string())
696        );
697    }
698
699    #[test]
700    fn test_json_data_type_checks() {
701        assert!(JsonData::null().is_null());
702        assert!(JsonData::bool(true).is_bool());
703        assert!(JsonData::float(42.0).unwrap().is_number());
704        assert!(JsonData::string("hello").is_string());
705        assert!(JsonData::array(vec![]).is_array());
706        assert!(JsonData::object(HashMap::new()).is_object());
707    }
708
709    #[test]
710    fn test_json_data_conversions() {
711        assert_eq!(JsonData::bool(true).as_bool(), Some(true));
712        assert_eq!(JsonData::float(42.0).unwrap().as_f64(), Some(42.0));
713        assert_eq!(JsonData::integer(42).as_i64(), Some(42));
714        assert_eq!(JsonData::string("hello").as_str(), Some("hello"));
715    }
716
717    #[test]
718    fn test_path_operations() {
719        let mut data = JsonData::object(HashMap::new());
720
721        // Set nested path
722        assert!(data.set_path("user.name", JsonData::string("John")));
723        assert!(data.set_path("user.age", JsonData::integer(30)));
724
725        // Get nested path
726        assert_eq!(data.path("user.name").unwrap().as_str(), Some("John"));
727        assert_eq!(data.path("user.age").unwrap().as_i64(), Some(30));
728
729        // Non-existent path
730        assert!(data.path("user.email").is_none());
731    }
732
733    #[test]
734    fn test_memory_size() {
735        let data = JsonData::object(
736            [
737                ("name".to_string(), JsonData::string("John")),
738                ("age".to_string(), JsonData::integer(30)),
739            ]
740            .into_iter()
741            .collect(),
742        );
743
744        assert!(data.memory_size() > 0);
745    }
746
747    #[test]
748    fn test_display() {
749        let data = JsonData::object(
750            [
751                ("name".to_string(), JsonData::string("John")),
752                ("active".to_string(), JsonData::bool(true)),
753            ]
754            .into_iter()
755            .collect(),
756        );
757
758        let display = format!("{data}");
759        assert!(display.contains("name"));
760        assert!(display.contains("John"));
761    }
762
763    #[test]
764    fn test_deserialize_rejects_nan_float() {
765        use serde::de::IntoDeserializer;
766        let deserializer: serde::de::value::F64Deserializer<serde::de::value::Error> =
767            f64::NAN.into_deserializer();
768        let result: Result<JsonData, _> = JsonData::deserialize(deserializer);
769        assert!(result.is_err());
770    }
771
772    #[test]
773    fn test_deserialize_rejects_infinite_float() {
774        use serde::de::IntoDeserializer;
775        let deserializer: serde::de::value::F64Deserializer<serde::de::value::Error> =
776            f64::INFINITY.into_deserializer();
777        let result: Result<JsonData, _> = JsonData::deserialize(deserializer);
778        assert!(result.is_err());
779    }
780
781    #[test]
782    fn test_deserialize_malformed_json_errors() {
783        let result: Result<JsonData, _> = serde_json::from_str("{not valid json");
784        assert!(result.is_err());
785    }
786
787    #[test]
788    fn test_deserialize_large_u64_becomes_float() {
789        let data: JsonData = serde_json::from_str(&u64::MAX.to_string()).unwrap();
790        assert!(matches!(data, JsonData::Float(_)));
791    }
792
793    #[test]
794    fn test_deserialize_unicode_string_roundtrip() {
795        let original = JsonData::string("Hello, 世界 🦀");
796        let json = serde_json::to_string(&original).unwrap();
797        let back: JsonData = serde_json::from_str(&json).unwrap();
798        assert_eq!(original, back);
799    }
800
801    #[test]
802    fn test_deserialize_deeply_nested_roundtrip() {
803        let mut data = JsonData::string("leaf");
804        for _ in 0..64 {
805            data = JsonData::array(vec![data]);
806        }
807        let json = serde_json::to_string(&data).unwrap();
808        let back: JsonData = serde_json::from_str(&json).unwrap();
809        assert_eq!(data, back);
810    }
811
812    #[test]
813    fn test_cautious_capacity_bounds_hostile_size_hint() {
814        // A malicious length-prefixed payload can claim any hint up to
815        // usize::MAX; the capped capacity must stay far below that,
816        // regardless of what the untrusted hint claims.
817        let capped = cautious_capacity::<JsonData>(Some(usize::MAX));
818        assert!(capped * size_of::<JsonData>() <= MAX_PREALLOC_BYTES);
819
820        let capped_pair = cautious_capacity::<(String, JsonData)>(Some(usize::MAX));
821        assert!(capped_pair * size_of::<(String, JsonData)>() <= MAX_PREALLOC_BYTES);
822
823        // A small, honest hint is never inflated beyond what was asked for.
824        assert_eq!(cautious_capacity::<JsonData>(Some(3)), 3);
825        assert_eq!(cautious_capacity::<JsonData>(None), 0);
826    }
827
828    #[test]
829    fn test_cautious_capacity_bounds_mid_range_hostile_size_hint() {
830        // usize::MAX overflows `capacity * size_of::<Element>()` and would
831        // panic with "capacity overflow" even without a cap, which doesn't
832        // exercise the actually-dangerous band: a hint large enough to
833        // succeed uncapped (100_000_000 elements * 56 bytes ~= 5.6 GB for
834        // `JsonData`) but far more than any legitimate payload needs.
835        let hint = 100_000_000;
836        let capped = cautious_capacity::<JsonData>(Some(hint));
837        assert!(capped < hint);
838        assert!(capped * size_of::<JsonData>() <= MAX_PREALLOC_BYTES);
839    }
840
841    /// Minimal hand-rolled `SeqAccess` that reports a hostile `size_hint()`
842    /// (`usize::MAX`) while only ever yielding `remaining` real elements —
843    /// simulates a length-prefixed format (MessagePack/CBOR) lying about
844    /// how many elements follow.
845    struct HostileSeqAccess {
846        remaining: usize,
847    }
848
849    impl<'de> SeqAccess<'de> for HostileSeqAccess {
850        type Error = serde_json::Error;
851
852        fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
853        where
854            T: serde::de::DeserializeSeed<'de>,
855        {
856            if self.remaining == 0 {
857                return Ok(None);
858            }
859            self.remaining -= 1;
860            seed.deserialize(serde_json::Value::Null).map(Some)
861        }
862
863        fn size_hint(&self) -> Option<usize> {
864            Some(usize::MAX)
865        }
866    }
867
868    struct HostileSeqDeserializer;
869
870    impl<'de> Deserializer<'de> for HostileSeqDeserializer {
871        type Error = serde_json::Error;
872
873        fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
874        where
875            V: Visitor<'de>,
876        {
877            visitor.visit_seq(HostileSeqAccess { remaining: 3 })
878        }
879
880        serde::forward_to_deserialize_any! {
881            bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
882            bytes byte_buf option unit unit_struct newtype_struct seq tuple
883            tuple_struct map struct enum identifier ignored_any
884        }
885    }
886
887    #[test]
888    fn test_visit_seq_ignores_hostile_size_hint() {
889        // If size_hint() were trusted directly, `Vec::with_capacity` would
890        // panic with "capacity overflow" (usize::MAX * size_of::<JsonData>()
891        // overflows) before reaching this assertion; see
892        // test_cautious_capacity_bounds_mid_range_hostile_size_hint for a
893        // hint that stays within range and would actually allocate.
894        let result: JsonData = JsonData::deserialize(HostileSeqDeserializer).unwrap();
895        assert_eq!(
896            result,
897            JsonData::Array(vec![JsonData::Null, JsonData::Null, JsonData::Null])
898        );
899    }
900
901    /// Minimal hand-rolled `MapAccess` counterpart to [`HostileSeqAccess`].
902    struct HostileMapAccess {
903        remaining: usize,
904    }
905
906    impl<'de> MapAccess<'de> for HostileMapAccess {
907        type Error = serde_json::Error;
908
909        fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
910        where
911            K: serde::de::DeserializeSeed<'de>,
912        {
913            if self.remaining == 0 {
914                return Ok(None);
915            }
916            self.remaining -= 1;
917            seed.deserialize(serde_json::Value::String(format!("k{}", self.remaining)))
918                .map(Some)
919        }
920
921        fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
922        where
923            V: serde::de::DeserializeSeed<'de>,
924        {
925            seed.deserialize(serde_json::Value::Null)
926        }
927
928        fn size_hint(&self) -> Option<usize> {
929            Some(usize::MAX)
930        }
931    }
932
933    struct HostileMapDeserializer;
934
935    impl<'de> Deserializer<'de> for HostileMapDeserializer {
936        type Error = serde_json::Error;
937
938        fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
939        where
940            V: Visitor<'de>,
941        {
942            visitor.visit_map(HostileMapAccess { remaining: 2 })
943        }
944
945        serde::forward_to_deserialize_any! {
946            bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
947            bytes byte_buf option unit unit_struct newtype_struct seq tuple
948            tuple_struct map struct enum identifier ignored_any
949        }
950    }
951
952    #[test]
953    fn test_visit_map_ignores_hostile_size_hint() {
954        let result: JsonData = JsonData::deserialize(HostileMapDeserializer).unwrap();
955        assert!(result.is_object());
956        assert_eq!(result.as_object().unwrap().len(), 2);
957    }
958
959    #[test]
960    fn test_serde_json_to_value_roundtrip_primitives() {
961        let data = JsonData::string("hello");
962        let value = serde_json::to_value(&data).unwrap();
963        let back = JsonData::from(value);
964        assert_eq!(data, back);
965    }
966
967    #[test]
968    fn test_serde_json_to_value_roundtrip_complex() {
969        let data = JsonData::object(
970            [
971                ("name".to_string(), JsonData::string("John")),
972                ("age".to_string(), JsonData::integer(30)),
973                ("active".to_string(), JsonData::bool(true)),
974            ]
975            .into_iter()
976            .collect(),
977        );
978
979        let value = serde_json::to_value(&data).unwrap();
980        let back = JsonData::from(value);
981        assert_eq!(data, back);
982    }
983
984    #[test]
985    fn test_non_finite_float_serializes_as_json_null() {
986        // JsonData::float() rejects NaN/infinite values, so this bypasses
987        // the validating constructor directly (only possible inside this
988        // crate) to exercise the Serialize impl's non-finite branch, which
989        // is the single canonical conversion path now that the divergent
990        // JsonAdapter::to_serde_value (NaN/Infinity -> 0) is removed.
991        for non_finite in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
992            let data = JsonData::Float(non_finite);
993            let value = serde_json::to_value(&data).unwrap();
994            assert_eq!(value, serde_json::Value::Null);
995        }
996    }
997
998    /// `levels` nested MessagePack fixarray-of-1 headers wrapping a nil.
999    fn nested_msgpack(levels: usize) -> Vec<u8> {
1000        let mut buf = vec![0x91u8; levels];
1001        buf.push(0xc0);
1002        buf
1003    }
1004
1005    #[test]
1006    fn test_deserialize_msgpack_at_max_depth_succeeds() {
1007        let data: Result<JsonData, _> =
1008            rmp_serde::from_slice(&nested_msgpack(MAX_DESERIALIZE_DEPTH));
1009        assert!(data.is_ok(), "{:?}", data.err());
1010    }
1011
1012    #[test]
1013    fn test_deserialize_msgpack_beyond_max_depth_rejected() {
1014        let err = rmp_serde::from_slice::<JsonData>(&nested_msgpack(MAX_DESERIALIZE_DEPTH + 1))
1015            .unwrap_err()
1016            .to_string();
1017        // Assert on our message, not just is_err(), so the test cannot pass
1018        // because rmp-serde rejected the input for an unrelated reason.
1019        assert!(err.contains("nesting depth"), "{err}");
1020    }
1021
1022    #[test]
1023    fn test_deserialize_json_beyond_max_depth_rejected() {
1024        let levels = MAX_DESERIALIZE_DEPTH + 1;
1025        let json = format!("{}1{}", "[".repeat(levels), "]".repeat(levels));
1026        let err = serde_json::from_str::<JsonData>(&json)
1027            .unwrap_err()
1028            .to_string();
1029        assert!(err.contains("nesting depth"), "{err}");
1030    }
1031
1032    /// Without the depth guard this aborts the test process with a stack
1033    /// overflow (verified). nextest runs each test in its own process, so the
1034    /// abort is reported as a failure rather than taking down the suite.
1035    ///
1036    /// 2 MiB gives real margin over the 64 levels of rmp-serde + visitor
1037    /// frames the guard actually needs (a 512 KiB stack was observed to work
1038    /// on macOS/arm64 debug builds, but with well under 2x headroom over the
1039    /// point it started overflowing — too tight to trust across a 3-OS CI
1040    /// matrix, where MSVC debug frames in particular run larger). 2 MiB is
1041    /// still far below the ~8 MiB platform default, so a regression that
1042    /// removed the guard and let this 100 000-level payload recurse
1043    /// unbounded would still overflow and fail the test.
1044    #[test]
1045    fn test_deserialize_extreme_nesting_does_not_overflow_stack() {
1046        let payload = nested_msgpack(100_000);
1047        let err = std::thread::Builder::new()
1048            .stack_size(2 * 1024 * 1024)
1049            .spawn(move || {
1050                rmp_serde::from_slice::<JsonData>(&payload)
1051                    .unwrap_err()
1052                    .to_string()
1053            })
1054            .expect("thread spawn")
1055            .join()
1056            .expect("worker must return an error, not overflow the stack");
1057        assert!(err.contains("nesting depth"), "{err}");
1058    }
1059
1060    /// Nested array32 headers each claiming ~16.7M elements: the depth guard,
1061    /// not the per-collection cap, is what stops this.
1062    #[test]
1063    fn test_deserialize_nested_large_size_hints_rejected_at_depth() {
1064        let mut buf = Vec::new();
1065        for _ in 0..200 {
1066            buf.extend_from_slice(&[0xdd, 0x00, 0xff, 0xff, 0xff]);
1067        }
1068        let err = rmp_serde::from_slice::<JsonData>(&buf)
1069            .unwrap_err()
1070            .to_string();
1071        assert!(err.contains("nesting depth"), "{err}");
1072    }
1073}