1
//! JSON parsing module for C API
2
//!
3
//! Re-exports the data types and serde_json implementations from `azul_core::json`.
4
//! Adds RefAny serialization support on top, including C API wrapper functions
5
//! (`refany_serialize_to_json`, `json_deserialize_to_refany`) and function pointer
6
//! types (`RefAnySerializeFnType`, `RefAnyDeserializeFnType`).
7

            
8
// Re-export all data types and methods from core
9
pub use azul_core::json::*;
10

            
11
use alloc::string::String;
12
use azul_css::AzString;
13

            
14
// ============================================================================
15
// Public API Functions
16
// ============================================================================
17

            
18
/// Parse a JSON string
19
#[cfg(feature = "json")]
20
#[must_use]
21
pub fn json_parse(s: &str) -> Result<Json, JsonParseError> {
22
    Json::parse(s)
23
}
24

            
25
/// Serialize JSON to string
26
#[cfg(feature = "json")]
27
pub fn json_stringify(json: &Json) -> AzString {
28
    json.to_json_string()
29
}
30

            
31
// ============================================================================
32
// RefAny JSON Serialization Support
33
// ============================================================================
34

            
35
use azul_core::refany::RefAny;
36

            
37
/// Result type for RefAny deserialization
38
#[derive(Debug, Clone)]
39
#[repr(C, u8)]
40
pub enum ResultRefAnyString {
41
    /// Successfully deserialized RefAny
42
    Ok(RefAny),
43
    /// Error message describing the failure
44
    Err(AzString),
45
}
46

            
47
impl ResultRefAnyString {
48
    /// Returns `true` if this is the `Ok` variant.
49
    pub fn is_ok(&self) -> bool {
50
        matches!(self, ResultRefAnyString::Ok(_))
51
    }
52

            
53
    /// Returns `true` if this is the `Err` variant.
54
    pub fn is_err(&self) -> bool {
55
        matches!(self, ResultRefAnyString::Err(_))
56
    }
57

            
58
    /// Converts into `Option<RefAny>`, discarding any error.
59
    pub fn ok(self) -> Option<RefAny> {
60
        match self {
61
            ResultRefAnyString::Ok(r) => Some(r),
62
            ResultRefAnyString::Err(_) => None,
63
        }
64
    }
65

            
66
    /// Converts into `Option<AzString>`, discarding any success value.
67
    pub fn err(self) -> Option<AzString> {
68
        match self {
69
            ResultRefAnyString::Ok(_) => None,
70
            ResultRefAnyString::Err(e) => Some(e),
71
        }
72
    }
73
}
74

            
75
/// C-compatible function type for serializing a RefAny's contents to JSON.
76
pub type RefAnySerializeFnType = extern "C" fn(RefAny) -> Json;
77

            
78
/// C-compatible function type for deserializing JSON into a new RefAny.
79
pub type RefAnyDeserializeFnType = extern "C" fn(Json) -> ResultRefAnyString;
80

            
81
/// Serialize a RefAny to JSON using its registered serialize function.
82
#[cfg(feature = "json")]
83
#[must_use]
84
pub fn serialize_refany_to_json(refany: &RefAny) -> Option<Json> {
85
    let serialize_fn = refany.get_serialize_fn();
86
    if serialize_fn == 0 {
87
        return None;
88
    }
89

            
90
    // Safety: `serialize_fn` is a valid `extern "C" fn(RefAny) -> Json` pointer,
91
    // set via `RefAny::set_serialize_fn`. The `!= 0` check above guards against null.
92
    let func: RefAnySerializeFnType = unsafe {
93
        core::mem::transmute(serialize_fn)
94
    };
95
    let json = func(refany.clone());
96

            
97
    if json.is_null() {
98
        None
99
    } else {
100
        Some(json)
101
    }
102
}
103

            
104
/// Deserialize JSON into a RefAny using the provided deserialize function.
105
#[cfg(feature = "json")]
106
#[must_use]
107
pub fn deserialize_refany_from_json(
108
    json: Json,
109
    deserialize_fn: usize
110
) -> Result<RefAny, String> {
111
    if deserialize_fn == 0 {
112
        return Err("Type does not support JSON deserialization".to_string());
113
    }
114

            
115
    // Safety: `deserialize_fn` is a valid `extern "C" fn(Json) -> ResultRefAnyString` pointer,
116
    // set via `RefAny::set_deserialize_fn`. The `== 0` check above guards against null.
117
    let func: RefAnyDeserializeFnType = unsafe {
118
        core::mem::transmute(deserialize_fn)
119
    };
120

            
121
    match func(json) {
122
        ResultRefAnyString::Ok(refany) => Ok(refany),
123
        ResultRefAnyString::Err(msg) => Err(msg.as_str().to_string()),
124
    }
125
}
126

            
127
impl From<Result<RefAny, String>> for ResultRefAnyString {
128
    fn from(result: Result<RefAny, String>) -> Self {
129
        match result {
130
            Ok(refany) => ResultRefAnyString::Ok(refany),
131
            Err(msg) => ResultRefAnyString::Err(AzString::from(msg)),
132
        }
133
    }
134
}
135

            
136
/// Serialize a RefAny to JSON, returns OptionJson::None if not supported or fails.
137
#[cfg(feature = "json")]
138
pub fn refany_serialize_to_json(refany: &RefAny) -> OptionJson {
139
    match serialize_refany_to_json(refany) {
140
        Some(json) => OptionJson::Some(json),
141
        None => OptionJson::None,
142
    }
143
}
144

            
145
/// Deserialize JSON into a RefAny using the provided deserialize function.
146
#[cfg(feature = "json")]
147
pub fn json_deserialize_to_refany(json: Json, deserialize_fn: usize) -> ResultRefAnyString {
148
    deserialize_refany_from_json(json, deserialize_fn).into()
149
}
150

            
151
/// Restores `state`'s contents in-place from `json`, using its registered
152
/// deserialize fn, and **preserves the live serialize/deserialize/update hooks**
153
/// across the swap (`replace_contents` copies them from the freshly-deserialized
154
/// value, which a non-`AZ_REFLECT`-upcast deserialize would leave unset). Returns
155
/// `Err` with a reason if `state` has no deserialize fn, the JSON can't be
156
/// deserialized, or the swap fails (active borrows).
157
///
158
/// Shared by [`RefAnyUndoManager`] and the AZ_DEBUG server's `set_app_state` /
159
/// `restore_snapshot` so both round-trip identically.
160
#[cfg(feature = "json")]
161
pub fn restore_refany_from_json(state: &mut RefAny, json: Json) -> Result<(), String> {
162
    let deser_fn = state.get_deserialize_fn();
163
    if deser_fn == 0 {
164
        return Err("state has no deserialize fn (AZ_REFLECT_JSON not registered)".to_string());
165
    }
166
    let restored = deserialize_refany_from_json(json, deser_fn)?;
167
    let ser_fn = state.get_serialize_fn();
168
    let upd_fn = state.get_update_fn();
169
    let ok = state.replace_contents(restored);
170
    state.set_serialize_fn(ser_fn);
171
    state.set_deserialize_fn(deser_fn);
172
    state.set_update_fn(upd_fn);
173
    if ok {
174
        Ok(())
175
    } else {
176
        Err("replace_contents failed (active borrows exist)".to_string())
177
    }
178
}
179

            
180
// ============================================================================
181
// Generic application-state undo/redo ("mini-git" with reversible JSON diffs)
182
// ============================================================================
183

            
184
/// Reversible JSON diffing for the undo history. A diff is a flat list of leaf
185
/// changes keyed by RFC-6901 JSON Pointer: objects diff per-key (recursively),
186
/// while scalars / arrays / type-changes are whole-value leaf replacements.
187
/// Each change records both `old` and `new`, so a diff applies forwards (redo)
188
/// or backwards (undo).
189
#[cfg(feature = "json")]
190
mod jsondiff {
191
    use alloc::{format, string::String, vec::Vec};
192

            
193
    use serde_json::Value;
194

            
195
    /// One reversible change at a JSON Pointer path. `old`/`new` are `None` when
196
    /// the key is absent on that side (key added / removed).
197
    #[derive(Debug, Clone)]
198
    pub struct Change {
199
        pub path: String,
200
        pub old: Option<Value>,
201
        pub new: Option<Value>,
202
    }
203

            
204
    /// Computes a reversible diff `old → new`.
205
    pub fn diff(old: &Value, new: &Value) -> Vec<Change> {
206
        let mut out = Vec::new();
207
        diff_rec(old, new, String::new(), &mut out);
208
        out
209
    }
210

            
211
    fn diff_rec(old: &Value, new: &Value, path: String, out: &mut Vec<Change>) {
212
        if old == new {
213
            return;
214
        }
215
        if let (Value::Object(o), Value::Object(n)) = (old, new) {
216
            for (k, ov) in o {
217
                let child = format!("{}/{}", path, esc(k));
218
                match n.get(k) {
219
                    Some(nv) => diff_rec(ov, nv, child, out),
220
                    None => out.push(Change { path: child, old: Some(ov.clone()), new: None }),
221
                }
222
            }
223
            for (k, nv) in n {
224
                if !o.contains_key(k) {
225
                    out.push(Change {
226
                        path: format!("{}/{}", path, esc(k)),
227
                        old: None,
228
                        new: Some(nv.clone()),
229
                    });
230
                }
231
            }
232
        } else {
233
            out.push(Change { path, old: Some(old.clone()), new: Some(new.clone()) });
234
        }
235
    }
236

            
237
    /// Applies a diff to `base`. `forward = true` moves `old → new` (redo);
238
    /// `forward = false` moves `new → old` (undo).
239
    pub fn apply(base: &Value, diff: &[Change], forward: bool) -> Value {
240
        let mut v = base.clone();
241
        for ch in diff {
242
            let target = if forward { &ch.new } else { &ch.old };
243
            set_at(&mut v, &ch.path, target);
244
        }
245
        v
246
    }
247

            
248
    fn set_at(root: &mut Value, path: &str, val: &Option<Value>) {
249
        if path.is_empty() {
250
            if let Some(v) = val {
251
                *root = v.clone();
252
            }
253
            return;
254
        }
255
        let split = path.rfind('/').unwrap_or(0);
256
        let parent_ptr = &path[..split];
257
        let last = unesc(&path[split + 1..]);
258
        let parent = if parent_ptr.is_empty() {
259
            root
260
        } else {
261
            match root.pointer_mut(parent_ptr) {
262
                Some(p) => p,
263
                None => return,
264
            }
265
        };
266
        if let Some(obj) = parent.as_object_mut() {
267
            match val {
268
                Some(v) => {
269
                    obj.insert(last, v.clone());
270
                }
271
                None => {
272
                    obj.remove(&last);
273
                }
274
            }
275
        }
276
    }
277

            
278
    fn esc(k: &str) -> String {
279
        k.replace('~', "~0").replace('/', "~1")
280
    }
281
    fn unesc(s: &str) -> String {
282
        s.replace("~1", "/").replace("~0", "~")
283
    }
284
}
285

            
286
/// A generic application-state undo/redo history — a "mini-git" for the app's
287
/// state `RefAny`, storing reversible **JSON diffs** between commits rather than
288
/// full snapshots (memory-efficient for large models like a text document).
289
///
290
/// Workflow: [`commit`](Self::commit) the current state at action / auto-save
291
/// boundaries (e.g. from a timer callback, or driven by the RefAny `update_fn`
292
/// hook marking the state dirty), then [`undo`](Self::undo) / [`redo`](Self::redo)
293
/// walk the history. Like git, committing a new state *after* an undo discards
294
/// the now-orphaned redo branch. Requires the state's JSON (de)serialize fns
295
/// (`AZ_REFLECT_JSON`); all ops are no-ops returning `false` otherwise.
296
///
297
/// Wired into `CallbackInfo` (`commit_undo_snapshot` / `undo` / `redo`) so a
298
/// callback — including a timer callback — can manage history on the app model.
299
#[cfg(feature = "json")]
300
#[derive(Debug, Clone, Default)]
301
pub struct RefAnyUndoManager {
302
    /// The last committed state (serde value): the base the next diff is computed
303
    /// against and that undo/redo diffs are applied to. `None` until first commit.
304
    head: Option<serde_json::Value>,
305
    /// Reversible diffs, each from commit N-1 → N (top = most recent).
306
    undo_diffs: alloc::vec::Vec<alloc::vec::Vec<jsondiff::Change>>,
307
    /// Diffs of undone commits, available to redo.
308
    redo_diffs: alloc::vec::Vec<alloc::vec::Vec<jsondiff::Change>>,
309
    /// Maximum number of undo diffs retained (`0` = unlimited).
310
    capacity: usize,
311
}
312

            
313
#[cfg(feature = "json")]
314
impl RefAnyUndoManager {
315
    /// Creates a history with a maximum depth (`0` = unlimited).
316
57
    pub fn new(capacity: usize) -> Self {
317
57
        Self {
318
57
            head: None,
319
57
            undo_diffs: alloc::vec::Vec::new(),
320
57
            redo_diffs: alloc::vec::Vec::new(),
321
57
            capacity,
322
57
        }
323
57
    }
324

            
325
    /// Commits `state` as a new history point, recording the reversible diff from
326
    /// the previous commit. The first commit just seeds the base. A commit that
327
    /// changed something discards the redo branch (git-like). Returns `true` if a
328
    /// commit was recorded (JSON supported and, after the first, state changed).
329
    pub fn commit(&mut self, state: &RefAny) -> bool {
330
        let cur = match serialize_refany_to_json(state) {
331
            Some(j) => j.to_serde_value(),
332
            None => return false,
333
        };
334
        match self.head.take() {
335
            None => {
336
                self.head = Some(cur); // seed base, no diff yet
337
                true
338
            }
339
            Some(prev) => {
340
                let d = jsondiff::diff(&prev, &cur);
341
                if d.is_empty() {
342
                    self.head = Some(prev);
343
                    return false; // nothing changed
344
                }
345
                self.undo_diffs.push(d);
346
                self.redo_diffs.clear(); // new commit orphans the redo branch
347
                if self.capacity != 0 && self.undo_diffs.len() > self.capacity {
348
                    self.undo_diffs.remove(0);
349
                }
350
                self.head = Some(cur);
351
                true
352
            }
353
        }
354
    }
355

            
356
    /// True if there is a commit to undo.
357
    pub fn can_undo(&self) -> bool {
358
        !self.undo_diffs.is_empty()
359
    }
360

            
361
    /// True if there is an undone commit to redo.
362
    pub fn can_redo(&self) -> bool {
363
        !self.redo_diffs.is_empty()
364
    }
365

            
366
    /// Reverts the most recent commit, restoring the previous state into `state`.
367
    pub fn undo(&mut self, state: &mut RefAny) -> bool {
368
        let d = match self.undo_diffs.pop() {
369
            Some(d) => d,
370
            None => return false,
371
        };
372
        let head = match self.head.take() {
373
            Some(h) => h,
374
            None => return false,
375
        };
376
        let reverted = jsondiff::apply(&head, &d, false);
377
        let ok = Self::restore(state, &reverted);
378
        self.head = Some(reverted);
379
        self.redo_diffs.push(d);
380
        ok
381
    }
382

            
383
    /// Re-applies the most recently undone commit.
384
    pub fn redo(&mut self, state: &mut RefAny) -> bool {
385
        let d = match self.redo_diffs.pop() {
386
            Some(d) => d,
387
            None => return false,
388
        };
389
        let head = match self.head.take() {
390
            Some(h) => h,
391
            None => return false,
392
        };
393
        let applied = jsondiff::apply(&head, &d, true);
394
        let ok = Self::restore(state, &applied);
395
        self.head = Some(applied);
396
        self.undo_diffs.push(d);
397
        ok
398
    }
399

            
400
    /// Drops all recorded history.
401
    pub fn clear(&mut self) {
402
        self.head = None;
403
        self.undo_diffs.clear();
404
        self.redo_diffs.clear();
405
    }
406

            
407
    fn restore(state: &mut RefAny, value: &serde_json::Value) -> bool {
408
        restore_refany_from_json(state, Json::from_serde_value(value.clone())).is_ok()
409
    }
410
}
411

            
412
// ============================================================================
413
// Tests
414
// ============================================================================
415

            
416
#[cfg(test)]
417
mod tests {
418
    use super::*;
419

            
420
    #[test]
421
    #[cfg(feature = "json")]
422
    fn test_parse_null() {
423
        let json = Json::parse("null").unwrap();
424
        assert!(json.is_null());
425
    }
426

            
427
    #[test]
428
    #[cfg(feature = "json")]
429
    fn test_parse_bool() {
430
        let json_true = Json::parse("true").unwrap();
431
        assert_eq!(json_true.as_bool().into_option(), Some(true));
432

            
433
        let json_false = Json::parse("false").unwrap();
434
        assert_eq!(json_false.as_bool().into_option(), Some(false));
435
    }
436

            
437
    #[test]
438
    #[cfg(feature = "json")]
439
    fn test_parse_number() {
440
        let json = Json::parse("42.5").unwrap();
441
        assert_eq!(json.as_number().into_option(), Some(42.5));
442

            
443
        let json_int = Json::parse("100").unwrap();
444
        assert_eq!(json_int.as_i64().into_option(), Some(100));
445
    }
446

            
447
    #[test]
448
    #[cfg(feature = "json")]
449
    fn test_parse_string() {
450
        let json = Json::parse("\"hello world\"").unwrap();
451
        assert_eq!(json.as_string().into_option().unwrap().as_str(), "hello world");
452
    }
453

            
454
    #[test]
455
    #[cfg(feature = "json")]
456
    fn test_parse_array() {
457
        let json = Json::parse("[1, 2, 3]").unwrap();
458
        assert!(json.is_array());
459
        assert_eq!(json.len(), 3);
460

            
461
        let first = json.get_index(0).unwrap();
462
        assert_eq!(first.as_number().into_option(), Some(1.0));
463
    }
464

            
465
    #[test]
466
    #[cfg(feature = "json")]
467
    fn test_parse_object() {
468
        let json = Json::parse(r#"{"name": "test", "value": 42}"#).unwrap();
469
        assert!(json.is_object());
470
        assert_eq!(json.len(), 2);
471

            
472
        let name = json.get_key("name").unwrap();
473
        assert_eq!(name.as_string().into_option().unwrap().as_str(), "test");
474

            
475
        let value = json.get_key("value").unwrap();
476
        assert_eq!(value.as_number().into_option(), Some(42.0));
477
    }
478

            
479
    #[test]
480
    #[cfg(feature = "json")]
481
    fn test_nested() {
482
        let json = Json::parse(r#"{"items": [1, 2, {"nested": true}]}"#).unwrap();
483

            
484
        let items = json.get_key("items").unwrap();
485
        assert!(items.is_array());
486

            
487
        let nested_obj = items.get_index(2).unwrap();
488
        let nested = nested_obj.get_key("nested").unwrap();
489
        assert_eq!(nested.as_bool().into_option(), Some(true));
490
    }
491

            
492
    #[test]
493
    #[cfg(feature = "json")]
494
    fn test_roundtrip_serde_parity() {
495
        // A nested value round-trips through pretty-print + re-parse unchanged —
496
        // exercises the AzJson <-> serde_json bridge in both directions.
497
        let src = r#"{"a":1,"b":[true,null,"x"],"c":{"d":2.5}}"#;
498
        let json = Json::parse(src).unwrap();
499
        let reparsed = Json::parse(json.to_string_pretty().as_str()).unwrap();
500
        assert_eq!(json, reparsed);
501
    }
502

            
503
    #[test]
504
    #[cfg(feature = "json")]
505
    fn test_undo_manager_roundtrip() {
506
        use azul_core::refany::RefAny;
507

            
508
        extern "C" fn ser(mut r: RefAny) -> Json {
509
            match r.downcast_ref::<i64>() {
510
                Some(v) => Json::integer(*v),
511
                None => Json::null(),
512
            }
513
        }
514
        extern "C" fn deser(j: Json) -> ResultRefAnyString {
515
            match j.as_i64().into_option() {
516
                Some(v) => Ok(RefAny::new(v)),
517
                None => Err("not an i64".to_string()),
518
            }
519
            .into()
520
        }
521

            
522
        let mut state = RefAny::new(10i64);
523
        state.set_serialize_fn(ser as usize);
524
        state.set_deserialize_fn(deser as usize);
525

            
526
        let mut undo = RefAnyUndoManager::new(0);
527
        undo.commit(&state); // commit 10 (seeds base)
528
        if let Some(mut v) = state.downcast_mut::<i64>() {
529
            *v = 20;
530
        }
531
        undo.commit(&state); // commit 20
532
        if let Some(mut v) = state.downcast_mut::<i64>() {
533
            *v = 30;
534
        }
535
        undo.commit(&state); // commit 30
536

            
537
        assert!(undo.can_undo());
538
        assert!(undo.undo(&mut state));
539
        assert_eq!(*state.downcast_ref::<i64>().unwrap(), 20);
540
        assert!(undo.undo(&mut state));
541
        assert_eq!(*state.downcast_ref::<i64>().unwrap(), 10);
542
        assert!(undo.redo(&mut state));
543
        assert_eq!(*state.downcast_ref::<i64>().unwrap(), 20);
544

            
545
        // mini-git branching: a new commit after an undo discards the orphaned
546
        // redo branch ("do a -> undo -> do b clears a").
547
        assert!(undo.can_redo()); // 30 is still redoable here
548
        if let Some(mut v) = state.downcast_mut::<i64>() {
549
            *v = 99;
550
        }
551
        undo.commit(&state); // branch from 20 -> 99
552
        assert!(!undo.can_redo()); // the 30 branch is gone
553
        assert!(undo.undo(&mut state));
554
        assert_eq!(*state.downcast_ref::<i64>().unwrap(), 20);
555
    }
556

            
557
    #[test]
558
    #[cfg(feature = "json")]
559
    fn test_json_diff_apply_reversible() {
560
        // A word-editor-like model: text + cursor + nested meta. The reversible
561
        // diff is the heart of the mini-git history, so it must round-trip both
562
        // directions.
563
        let a = Json::parse(r#"{"text":"hello","cursor":0,"meta":{"saved":true}}"#)
564
            .unwrap()
565
            .to_serde_value();
566
        let b = Json::parse(
567
            r#"{"text":"hello world","cursor":11,"meta":{"saved":false},"tags":[1,2]}"#,
568
        )
569
        .unwrap()
570
        .to_serde_value();
571
        let d = super::jsondiff::diff(&a, &b);
572
        assert!(!d.is_empty());
573
        assert_eq!(super::jsondiff::apply(&a, &d, true), b); // forward: a -> b
574
        assert_eq!(super::jsondiff::apply(&b, &d, false), a); // backward: b -> a
575
        assert!(super::jsondiff::diff(&a, &a).is_empty()); // unchanged -> empty diff
576
    }
577

            
578
    #[test]
579
    #[cfg(feature = "json")]
580
    fn test_parse_error() {
581
        let result = Json::parse("{ invalid }");
582
        assert!(result.is_err());
583

            
584
        let err = result.unwrap_err();
585
        assert!(err.line > 0);
586
    }
587
}
588

            
589
// ============================================================================
590
// Adversarial tests (autotest)
591
// ============================================================================
592

            
593
/// Adversarial coverage for this module. The whole file is already behind
594
/// `#[cfg(feature = "json")]` (see `lib.rs`), so no per-test feature gate is
595
/// needed here.
596
///
597
/// Deliberately NOT tested: passing a non-zero *bogus* `usize` as a
598
/// `deserialize_fn`. `deserialize_refany_from_json` transmutes it to a function
599
/// pointer and calls it, so any value other than `0` or a real
600
/// `extern "C" fn(Json) -> ResultRefAnyString` is UB by contract, not a case the
601
/// function can defend against.
602
#[cfg(test)]
603
mod autotest_generated {
604
    use super::*;
605

            
606
    // ------------------------------------------------------------------
607
    // Fixtures
608
    // ------------------------------------------------------------------
609

            
610
    /// The canonical AZ_REFLECT_JSON pair for an `i64` state.
611
    extern "C" fn ser_i64(mut r: RefAny) -> Json {
612
        match r.downcast_ref::<i64>() {
613
            Some(v) => Json::integer(*v),
614
            None => Json::null(),
615
        }
616
    }
617

            
618
    /// Rejects anything that is not an exactly-representable `i64`.
619
    extern "C" fn deser_i64(j: Json) -> ResultRefAnyString {
620
        match j.as_i64().into_option() {
621
            Some(v) => ResultRefAnyString::Ok(RefAny::new(v)),
622
            None => ResultRefAnyString::Err(AzString::from("not an i64".to_string())),
623
        }
624
    }
625

            
626
    /// A serializer that always yields JSON `null` — i.e. "no JSON form".
627
    extern "C" fn ser_null(_r: RefAny) -> Json {
628
        Json::null()
629
    }
630

            
631
    /// A deserializer that always fails.
632
    extern "C" fn deser_always_err(_j: Json) -> ResultRefAnyString {
633
        ResultRefAnyString::Err(AzString::from("always fails".to_string()))
634
    }
635

            
636
    /// An object-shaped state whose JSON key is hostile to JSON Pointer syntax
637
    /// (contains both `/` and `~`), so the diff path must be escaped/unescaped.
638
    #[derive(Debug, Clone, PartialEq)]
639
    struct Doc {
640
        text: String,
641
        cursor: i64,
642
    }
643

            
644
    extern "C" fn ser_doc(mut r: RefAny) -> Json {
645
        let (text, cursor) = match r.downcast_ref::<Doc>() {
646
            Some(d) => (d.text.clone(), d.cursor),
647
            None => return Json::null(),
648
        };
649
        Json::object(JsonKeyValueVec::from_vec(vec![
650
            JsonKeyValue::create(AzString::from("a/b~c".to_string()), Json::string(text)),
651
            JsonKeyValue::create(AzString::from("cursor".to_string()), Json::integer(cursor)),
652
        ]))
653
    }
654

            
655
    extern "C" fn deser_doc(j: Json) -> ResultRefAnyString {
656
        let text = match j.get_key("a/b~c").and_then(|t| t.as_string().into_option()) {
657
            Some(s) => s.as_str().to_string(),
658
            None => return ResultRefAnyString::Err(AzString::from("missing text".to_string())),
659
        };
660
        let cursor = match j.get_key("cursor").and_then(|c| c.as_i64().into_option()) {
661
            Some(c) => c,
662
            None => return ResultRefAnyString::Err(AzString::from("missing cursor".to_string())),
663
        };
664
        ResultRefAnyString::Ok(RefAny::new(Doc { text, cursor }))
665
    }
666

            
667
    fn state_i64(v: i64) -> RefAny {
668
        let mut s = RefAny::new(v);
669
        s.set_serialize_fn(ser_i64 as usize);
670
        s.set_deserialize_fn(deser_i64 as usize);
671
        s
672
    }
673

            
674
    fn read_i64(s: &mut RefAny) -> i64 {
675
        let g = s.downcast_ref::<i64>().expect("state holds an i64");
676
        *g
677
    }
678

            
679
    fn write_i64(s: &mut RefAny, v: i64) {
680
        let mut g = s.downcast_mut::<i64>().expect("state holds an i64");
681
        *g = v;
682
    }
683

            
684
    // ------------------------------------------------------------------
685
    // json_parse — parser: malformed / huge / boundary / unicode
686
    // ------------------------------------------------------------------
687

            
688
    #[test]
689
    fn json_parse_empty_and_whitespace_only_is_err() {
690
        for src in ["", " ", "   ", "\t\n\r ", "\u{feff}"] {
691
            let r = json_parse(src);
692
            assert!(r.is_err(), "{src:?} must be rejected, got {r:?}");
693
        }
694
    }
695

            
696
    #[test]
697
    fn json_parse_garbage_is_err_and_never_panics() {
698
        for src in [
699
            "{ invalid }",
700
            "]]]",
701
            "}{",
702
            "nul",
703
            "tru",
704
            "'single quoted'",
705
            "{\"a\":1,}",
706
            "[1,2,]",
707
            "{a:1}",
708
            "\u{0}\u{1}\u{2}",
709
            "\\",
710
            "\"unterminated",
711
            "01",
712
            "--1",
713
            "[,]",
714
            "{\"a\"}",
715
            "{\"a\":}",
716
        ] {
717
            let r = json_parse(src);
718
            assert!(r.is_err(), "{src:?} must be rejected, got {r:?}");
719
        }
720
    }
721

            
722
    #[test]
723
    fn json_parse_leading_trailing_junk_is_deterministic() {
724
        // Surrounding whitespace is fine ...
725
        let ok = json_parse("  \n\t{\"a\":1}  \n").expect("whitespace-padded JSON parses");
726
        assert!(ok.is_object());
727
        assert_eq!(ok.len(), 1);
728

            
729
        // ... trailing non-whitespace is not.
730
        for src in ["{\"a\":1};garbage", "[1,2,3]extra", "null null", "1 2", "\"a\"\"b\""] {
731
            assert!(json_parse(src).is_err(), "{src:?} must be rejected");
732
        }
733
    }
734

            
735
    #[test]
736
    fn json_parse_deeply_nested_is_rejected_without_stack_overflow() {
737
        // serde_json's default recursion limit (128) must turn this into an
738
        // `Err` rather than blowing the stack.
739
        let deep_arrays = format!("{}{}", "[".repeat(10_000), "]".repeat(10_000));
740
        assert!(json_parse(&deep_arrays).is_err(), "10k-deep arrays must be rejected");
741

            
742
        let deep_objects = format!(
743
            "{}1{}",
744
            "{\"a\":".repeat(10_000),
745
            "}".repeat(10_000)
746
        );
747
        assert!(json_parse(&deep_objects).is_err(), "10k-deep objects must be rejected");
748

            
749
        // Unbalanced garbage of the same shape: still an error, still no crash.
750
        assert!(json_parse(&"[".repeat(1_000_000)).is_err());
751

            
752
        // Positive control: a nesting depth inside the limit still parses.
753
        let shallow = format!("{}1{}", "[".repeat(64), "]".repeat(64));
754
        assert!(json_parse(&shallow).expect("64-deep parses").is_array());
755
    }
756

            
757
    #[test]
758
    fn json_parse_extremely_long_input_does_not_panic_or_hang() {
759
        // 1M-char string payload.
760
        let long = "a".repeat(1_000_000);
761
        let j = json_parse(&format!("\"{long}\"")).expect("1M-char string parses");
762
        assert_eq!(
763
            j.as_string().into_option().expect("string").as_str().len(),
764
            1_000_000
765
        );
766

            
767
        // 50k-element array (each `len()` call re-parses the payload).
768
        let arr_src = format!("[{}]", vec!["0"; 50_000].join(","));
769
        let arr = json_parse(&arr_src).expect("50k-element array parses");
770
        assert!(arr.is_array());
771
        assert_eq!(arr.len(), 50_000);
772
        assert_eq!(arr.get_index(49_999).expect("last").as_i64().into_option(), Some(0));
773
        assert!(arr.get_index(50_000).is_none(), "out-of-range index must be None");
774
        assert!(arr.get_index(usize::MAX).is_none(), "usize::MAX index must be None");
775
    }
776

            
777
    #[test]
778
    fn json_parse_boundary_numbers() {
779
        assert_eq!(json_parse("0").expect("0").as_i64().into_option(), Some(0));
780

            
781
        // "-0": the sign of zero is not preserved through the f64 store.
782
        let neg_zero = json_parse("-0").expect("-0");
783
        assert_eq!(neg_zero.as_number().into_option(), Some(0.0));
784
        assert_eq!(json_stringify(&neg_zero).as_str(), "0");
785

            
786
        // i64::MIN is exactly -2^63 in f64, so it round-trips.
787
        let min = json_parse(&i64::MIN.to_string()).expect("i64::MIN");
788
        assert_eq!(min.as_i64().into_option(), Some(i64::MIN));
789

            
790
        // i64::MAX is NOT exactly representable — f64 rounds it up to 2^63.
791
        // `as_i64` must refuse it (None) rather than wrap around to i64::MIN.
792
        let max = json_parse(&i64::MAX.to_string()).expect("i64::MAX");
793
        assert_eq!(max.as_number().into_option(), Some(i64::MAX as f64));
794
        assert_eq!(max.as_i64().into_option(), None);
795

            
796
        // u64::MAX is far out of i64 range: None, never a wrapped negative.
797
        let umax = json_parse(&u64::MAX.to_string()).expect("u64::MAX");
798
        assert_eq!(umax.as_i64().into_option(), None);
799
        assert!(umax.as_number().into_option().expect("number") > 0.0);
800

            
801
        // Smallest subnormal and largest finite double stay finite and typed.
802
        let tiny = json_parse("5e-324").expect("subnormal");
803
        let t = tiny.as_number().into_option().expect("number");
804
        assert!(t > 0.0 && t.is_finite(), "subnormal decoded to {t}");
805
        let big = json_parse("1.7976931348623157e308").expect("f64::MAX");
806
        assert!(big.as_number().into_option().expect("number").is_finite());
807

            
808
        // Non-integral numbers are numbers, but never integers.
809
        let frac = json_parse("0.5").expect("0.5");
810
        assert_eq!(frac.as_number().into_option(), Some(0.5));
811
        assert_eq!(frac.as_i64().into_option(), None);
812
    }
813

            
814
    #[test]
815
    fn json_parse_rejects_non_json_number_literals() {
816
        for src in [
817
            "NaN", "nan", "Infinity", "-Infinity", "inf", "-inf", "+1", ".5", "5.", "1e",
818
            "1e+", "0x10", "1_000", "1,0",
819
        ] {
820
            assert!(json_parse(src).is_err(), "{src:?} is not valid JSON");
821
        }
822
    }
823

            
824
    #[test]
825
    fn json_parse_huge_magnitude_numbers_do_not_panic() {
826
        let nines = "9".repeat(400);
827
        for src in ["1e309", "-1e309", "1e-400", nines.as_str()] {
828
            match json_parse(src) {
829
                Err(_) => {} // rejecting is fine
830
                Ok(j) => {
831
                    // Whatever it decodes to, it is never a NaN and always
832
                    // stringifies without panicking.
833
                    if let Some(n) = j.as_number().into_option() {
834
                        assert!(!n.is_nan(), "{src:?} decoded to NaN");
835
                    }
836
                    assert!(!json_stringify(&j).as_str().is_empty());
837
                }
838
            }
839
        }
840
    }
841

            
842
    #[test]
843
    fn json_parse_unicode_payloads() {
844
        // Astral-plane emoji.
845
        let emoji = json_parse("\"\u{1F600}\"").expect("emoji");
846
        assert_eq!(
847
            emoji.as_string().into_option().expect("string").as_str(),
848
            "\u{1F600}"
849
        );
850

            
851
        // Combining marks in a key, bidi override + ZWJ in a value.
852
        let obj = json_parse("{\"e\u{301}\":\"\u{202E}abc\u{200D}\u{1F468}\"}")
853
            .expect("combining / bidi payload");
854
        assert!(obj.is_object());
855
        assert_eq!(obj.len(), 1);
856
        assert!(obj.get_key("e\u{301}").is_some());
857
        assert!(obj.get_key("e").is_none(), "key lookup must not normalize");
858

            
859
        // Escaped NUL survives the round trip.
860
        let nul = json_parse("\"\\u0000\"").expect("escaped NUL");
861
        assert_eq!(nul.as_string().into_option().expect("string").as_str(), "\0");
862
        assert_eq!(json_parse(json_stringify(&nul).as_str()).expect("re-parse"), nul);
863

            
864
        // Broken escapes / lone surrogates are rejected, not silently replaced.
865
        for src in ["\"\\ud800\"", "\"\\udfff\"", "\"\\x\"", "\"\\u00\""] {
866
            assert!(json_parse(src).is_err(), "{src:?} must be rejected");
867
        }
868
    }
869

            
870
    #[test]
871
    fn json_parse_error_is_reported_with_a_position() {
872
        let e = json_parse("{\n  \"a\": ,\n}").unwrap_err();
873
        assert!(e.line >= 1, "line must be reported");
874
        assert!(!e.message.as_str().is_empty());
875
        // Display impl must not panic on either shape.
876
        assert!(!format!("{e}").is_empty());
877
    }
878

            
879
    // ------------------------------------------------------------------
880
    // json_stringify / round-trip: encode == decode
881
    // ------------------------------------------------------------------
882

            
883
    #[test]
884
    fn json_roundtrip_parse_stringify_parse_is_stable() {
885
        for src in [
886
            "null",
887
            "true",
888
            "false",
889
            "0",
890
            "-1",
891
            "1.5",
892
            "\"\"",
893
            "\"\\n\\t\\\"\\\\\"",
894
            "[]",
895
            "{}",
896
            "[1,2,3]",
897
            "{\"a\":1}",
898
            "{\"a\":[1,{\"b\":null}],\"c\":\"\u{1F600}\"}",
899
            "[[[[[1]]]]]",
900
        ] {
901
            let a = json_parse(src).unwrap_or_else(|e| panic!("{src:?} must parse: {e}"));
902
            let encoded = json_stringify(&a);
903
            let b = json_parse(encoded.as_str())
904
                .unwrap_or_else(|e| panic!("re-parse of {:?} failed: {e}", encoded.as_str()));
905
            assert_eq!(a, b, "value changed across encode -> decode for {src:?}");
906
            assert_eq!(
907
                encoded.as_str(),
908
                json_stringify(&b).as_str(),
909
                "stringify is not idempotent for {src:?}"
910
            );
911
        }
912
    }
913

            
914
    #[test]
915
    fn json_roundtrip_constructed_values() {
916
        for v in [
917
            Json::null(),
918
            Json::bool(true),
919
            Json::bool(false),
920
            Json::integer(0),
921
            Json::integer(-1),
922
            Json::integer(i64::MIN),
923
            // i64::MAX is stored as 2^63 and emitted as such — it still decodes
924
            // back to the identical (lossy) f64, so the round trip is stable.
925
            Json::integer(i64::MAX),
926
            Json::number(1.5),
927
            Json::number(-0.0),
928
            Json::number(1e300),
929
            Json::number(f64::MAX),
930
            Json::number(f64::MIN_POSITIVE),
931
            Json::string(""),
932
            Json::string("quote\" backslash\\ newline\n tab\t"),
933
            Json::string("\u{1F600}e\u{301}\u{0}"),
934
        ] {
935
            let encoded = json_stringify(&v);
936
            let back = json_parse(encoded.as_str()).unwrap_or_else(|e| {
937
                panic!("{:?} did not re-parse: {e}", encoded.as_str())
938
            });
939
            assert_eq!(v, back, "{:?} did not survive the round trip", encoded.as_str());
940
        }
941
    }
942

            
943
    #[test]
944
    fn json_stringify_non_finite_numbers_does_not_panic() {
945
        // JSON has no representation for NaN / ±Infinity. `to_json_string` falls
946
        // back to Rust's float `Display` ("NaN" / "inf"), which is *not* valid
947
        // JSON, so such a value does not round-trip. Pinned as observed
948
        // behaviour: the hard requirement is only that nothing panics, and that
949
        // the serde bridge maps them to `null` instead of emitting garbage.
950
        for v in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
951
            let j = Json::number(v);
952
            let encoded = json_stringify(&j);
953
            assert!(!encoded.as_str().is_empty());
954
            let _ = json_parse(encoded.as_str()); // allowed to fail, must not panic
955
            assert!(j.to_serde_value().is_null(), "serde bridge must null out {v}");
956
        }
957
    }
958

            
959
    #[test]
960
    fn json_with_corrupt_ffi_payload_does_not_panic() {
961
        // `Json`'s fields are `pub` (C ABI), so a C caller can hand us an
962
        // Array/Object whose serialized payload is not JSON at all. Every
963
        // accessor must degrade gracefully rather than panic.
964
        for value_type in [JsonType::Array, JsonType::Object] {
965
            let bogus = Json {
966
                value_type,
967
                internal: JsonInternal {
968
                    string_value: AzString::from("not-json".to_string()),
969
                    number_value: f64::NAN,
970
                    bool_value: true,
971
                },
972
            };
973
            assert_eq!(bogus.len(), 0);
974
            assert!(bogus.is_empty());
975
            assert!(bogus.get_index(0).is_none());
976
            assert!(bogus.get_key("a").is_none());
977
            assert!(bogus.keys().is_empty());
978
            assert!(bogus.to_serde_value().is_null());
979
            assert!(!json_stringify(&bogus).as_str().is_empty());
980
            assert!(!bogus.to_string_pretty().as_str().is_empty());
981
            // Wrong-type accessors stay None instead of reading the wrong union arm.
982
            assert_eq!(bogus.as_bool().into_option(), None);
983
            assert_eq!(bogus.as_number().into_option(), None);
984
            assert_eq!(bogus.as_i64().into_option(), None);
985
            assert_eq!(bogus.as_string().into_option(), None);
986
        }
987
    }
988

            
989
    // ------------------------------------------------------------------
990
    // ResultRefAnyString — predicates / getters: invariants
991
    // ------------------------------------------------------------------
992

            
993
    #[test]
994
    fn result_refany_string_predicates_are_mutually_exclusive() {
995
        let ok = ResultRefAnyString::Ok(RefAny::new(1i64));
996
        assert!(ok.is_ok());
997
        assert!(!ok.is_err());
998

            
999
        // Edge payload: an *empty* error message is still the Err variant.
        let err = ResultRefAnyString::Err(AzString::from(String::new()));
        assert!(err.is_err());
        assert!(!err.is_ok());
        // ok()/err() partition exactly one value into exactly one Option.
        assert!(ok.clone().ok().is_some());
        assert!(ok.clone().err().is_none());
        assert!(err.clone().ok().is_none());
        assert_eq!(err.clone().err().expect("err").as_str(), "");
    }
    #[test]
    fn result_refany_string_err_payload_edges() {
        // Huge multi-byte error message: no truncation, no panic.
        let big = "\u{1F600}".repeat(100_000);
        let e = ResultRefAnyString::Err(AzString::from(big.clone()));
        assert!(e.is_err());
        assert_eq!(e.err().expect("err").as_str().len(), big.len());
    }
    #[test]
    fn result_refany_string_from_result_parity() {
        let ok: Result<RefAny, String> = Ok(RefAny::new(7i64));
        let ok: ResultRefAnyString = ok.into();
        assert!(ok.is_ok() && !ok.is_err());
        let mut inner = ok.ok().expect("ok");
        assert_eq!(read_i64(&mut inner), 7);
        let err: Result<RefAny, String> = Err("boom".to_string());
        let err: ResultRefAnyString = err.into();
        assert!(err.is_err() && !err.is_ok());
        assert_eq!(err.err().expect("err").as_str(), "boom");
    }
    #[test]
    fn result_refany_string_clone_keeps_the_payload_alive() {
        let r = ResultRefAnyString::Ok(RefAny::new(7i64));
        for _ in 0..64 {
            let c = r.clone();
            assert!(c.is_ok());
        }
        // 64 clone/drop cycles later the shared data is intact (no double-free)
        // and no clone leaked a strong reference.
        let mut inner = r.ok().expect("ok");
        assert_eq!(read_i64(&mut inner), 7);
        assert_eq!(inner.get_ref_count(), 1);
    }
    // ------------------------------------------------------------------
    // serialize_refany_to_json / refany_serialize_to_json
    // ------------------------------------------------------------------
    #[test]
    fn serialize_refany_without_hook_is_none() {
        let plain = RefAny::new(5i64);
        assert!(serialize_refany_to_json(&plain).is_none());
        assert!(matches!(refany_serialize_to_json(&plain), OptionJson::None));
    }
    #[test]
    fn serialize_refany_with_hook_agrees_with_the_c_wrapper() {
        let s = state_i64(42);
        let j = serialize_refany_to_json(&s).expect("hook is set");
        assert_eq!(j.as_i64().into_option(), Some(42));
        match refany_serialize_to_json(&s) {
            OptionJson::Some(j2) => assert_eq!(j2, j),
            OptionJson::None => panic!("hook is set, must be Some"),
        }
        // The temporary clone handed to the hook must be released again.
        assert_eq!(s.get_ref_count(), 1);
    }
    #[test]
    fn serialize_refany_null_result_is_treated_as_unsupported() {
        let mut s = RefAny::new(5i64);
        s.set_serialize_fn(ser_null as usize);
        assert!(serialize_refany_to_json(&s).is_none());
        assert!(matches!(refany_serialize_to_json(&s), OptionJson::None));
    }
    // ------------------------------------------------------------------
    // deserialize_refany_from_json / json_deserialize_to_refany — numeric
    // ------------------------------------------------------------------
    #[test]
    fn deserialize_refany_with_zero_fn_pointer_is_err() {
        let e = deserialize_refany_from_json(Json::integer(1), 0).unwrap_err();
        assert_eq!(e, "Type does not support JSON deserialization");
        let r = json_deserialize_to_refany(Json::null(), 0);
        assert!(r.is_err());
        assert_eq!(
            r.err().expect("err").as_str(),
            "Type does not support JSON deserialization"
        );
    }
    #[test]
    fn deserialize_refany_numeric_boundaries() {
        let fnptr = deser_i64 as usize;
        let mut zero = deserialize_refany_from_json(Json::integer(0), fnptr).expect("0");
        assert_eq!(read_i64(&mut zero), 0);
        let mut neg = deserialize_refany_from_json(Json::integer(-1), fnptr).expect("-1");
        assert_eq!(read_i64(&mut neg), -1);
        // i64::MIN is exactly -2^63 in f64 and survives.
        let mut min = deserialize_refany_from_json(Json::integer(i64::MIN), fnptr)
            .expect("i64::MIN");
        assert_eq!(read_i64(&mut min), i64::MIN);
        // i64::MAX rounds up to 2^63 in the f64 store, which is out of range —
        // it must be reported as an error, never wrapped around to i64::MIN.
        assert!(deserialize_refany_from_json(Json::integer(i64::MAX), fnptr).is_err());
        // Saturation / NaN / non-integral / wrong-type inputs all fail cleanly.
        for j in [
            Json::number(f64::NAN),
            Json::number(f64::INFINITY),
            Json::number(f64::NEG_INFINITY),
            Json::number(1e300),
            Json::number(-1e300),
            Json::number(0.5),
            Json::null(),
            Json::bool(true),
            Json::string("7"),
        ] {
            let described = json_stringify(&j).as_str().to_string();
            let r = json_deserialize_to_refany(j, fnptr);
            assert!(r.is_err(), "{described} must not deserialize into an i64");
            assert_eq!(r.err().expect("err").as_str(), "not an i64");
        }
    }
    #[test]
    fn deserialize_refany_propagates_the_hook_error_message() {
        let r = json_deserialize_to_refany(Json::integer(1), deser_always_err as usize);
        assert!(r.is_err() && !r.is_ok());
        assert_eq!(r.err().expect("err").as_str(), "always fails");
    }
    // ------------------------------------------------------------------
    // restore_refany_from_json
    // ------------------------------------------------------------------
    #[test]
    fn restore_refany_without_deserialize_fn_is_err_and_leaves_state_intact() {
        let mut plain = RefAny::new(1i64);
        let e = restore_refany_from_json(&mut plain, Json::integer(2)).unwrap_err();
        assert!(e.contains("no deserialize fn"), "unexpected message: {e}");
        assert_eq!(read_i64(&mut plain), 1);
    }
    #[test]
    fn restore_refany_preserves_the_hooks_across_the_swap() {
        let mut s = state_i64(1);
        let (ser, deser) = (s.get_serialize_fn(), s.get_deserialize_fn());
        restore_refany_from_json(&mut s, Json::integer(2)).expect("restore");
        assert_eq!(read_i64(&mut s), 2);
        assert_eq!(s.get_serialize_fn(), ser, "serialize hook was lost");
        assert_eq!(s.get_deserialize_fn(), deser, "deserialize hook was lost");
        assert!(s.can_serialize() && s.can_deserialize());
        // ... and the restored value is itself serializable again.
        assert_eq!(
            serialize_refany_to_json(&s).expect("still serializable").as_i64().into_option(),
            Some(2)
        );
    }
    #[test]
    fn restore_refany_rejects_json_the_hook_refuses() {
        let mut s = state_i64(3);
        let e = restore_refany_from_json(&mut s, Json::string("nope")).unwrap_err();
        assert_eq!(e, "not an i64");
        assert_eq!(read_i64(&mut s), 3, "state must be untouched on failure");
    }
    #[test]
    fn restore_refany_fails_while_a_borrow_is_live() {
        let mut s = state_i64(4);
        let mut sibling = s.clone(); // clones share the RefCountInner
        let guard = sibling.downcast_ref::<i64>().expect("shared borrow");
        let e = restore_refany_from_json(&mut s, Json::integer(9)).unwrap_err();
        assert!(e.contains("replace_contents failed"), "unexpected message: {e}");
        assert_eq!(*guard, 4, "the live borrow must still see the old value");
        drop(guard);
        // Once the borrow is released the very same call succeeds, and every
        // clone observes the swap.
        restore_refany_from_json(&mut s, Json::integer(9)).expect("restore after drop");
        assert_eq!(read_i64(&mut s), 9);
        assert_eq!(read_i64(&mut sibling), 9);
    }
    // ------------------------------------------------------------------
    // RefAnyUndoManager — constructor + predicates + invariants
    // ------------------------------------------------------------------
    #[test]
    fn undo_manager_new_holds_its_invariants() {
        for capacity in [0usize, 1, 2, usize::MAX] {
            let m = RefAnyUndoManager::new(capacity);
            assert_eq!(m.capacity, capacity);
            assert!(m.head.is_none());
            assert!(m.undo_diffs.is_empty());
            assert!(m.redo_diffs.is_empty());
            assert!(!m.can_undo());
            assert!(!m.can_redo());
        }
        let d = RefAnyUndoManager::default();
        assert!(!d.can_undo() && !d.can_redo());
    }
    #[test]
    fn undo_manager_ops_on_empty_history_are_noops() {
        let mut m = RefAnyUndoManager::new(0);
        let mut s = state_i64(5);
        assert!(!m.undo(&mut s));
        assert!(!m.redo(&mut s));
        m.clear(); // clearing an empty history must not panic
        assert!(!m.can_undo() && !m.can_redo());
        assert_eq!(read_i64(&mut s), 5, "state must be untouched");
    }
    #[test]
    fn undo_manager_requires_a_json_representation() {
        let mut m = RefAnyUndoManager::new(0);
        let plain = RefAny::new(1i64); // no serialize hook at all
        assert!(!m.commit(&plain));
        assert!(!m.can_undo());
        assert!(m.head.is_none());
        let mut null_ser = RefAny::new(1i64); // hook exists but yields `null`
        null_ser.set_serialize_fn(ser_null as usize);
        assert!(!m.commit(&null_ser));
        assert!(m.head.is_none(), "a null body must not seed the history");
    }
    #[test]
    fn undo_manager_unchanged_commit_keeps_the_redo_branch() {
        let mut m = RefAnyUndoManager::new(0);
        let mut s = state_i64(1);
        assert!(m.commit(&s)); // seeds the base
        write_i64(&mut s, 2);
        assert!(m.commit(&s));
        assert!(m.undo(&mut s));
        assert_eq!(read_i64(&mut s), 1);
        assert!(m.can_redo());
        // Re-committing an *unchanged* state records nothing and must not
        // orphan the redo branch.
        assert!(!m.commit(&s));
        assert!(m.can_redo());
        assert!(m.redo(&mut s));
        assert_eq!(read_i64(&mut s), 2);
        assert!(!m.can_redo());
    }
    #[test]
    fn undo_manager_capacity_caps_the_retained_history() {
        let mut m = RefAnyUndoManager::new(2);
        let mut s = state_i64(0);
        assert!(m.commit(&s));
        for v in 1..=5i64 {
            write_i64(&mut s, v);
            assert!(m.commit(&s));
        }
        assert_eq!(m.undo_diffs.len(), 2, "capacity must evict the oldest diffs");
        // Only the two most recent steps are reachable: 5 -> 4 -> 3.
        assert!(m.undo(&mut s));
        assert_eq!(read_i64(&mut s), 4);
        assert!(m.undo(&mut s));
        assert_eq!(read_i64(&mut s), 3);
        assert!(!m.can_undo());
        assert!(!m.undo(&mut s), "an exhausted history returns false, not a panic");
        assert_eq!(read_i64(&mut s), 3);
    }
    #[test]
    fn undo_manager_capacity_one_keeps_exactly_one_step() {
        let mut m = RefAnyUndoManager::new(1);
        let mut s = state_i64(0);
        assert!(m.commit(&s));
        for v in 1..=3i64 {
            write_i64(&mut s, v);
            assert!(m.commit(&s));
        }
        assert_eq!(m.undo_diffs.len(), 1);
        assert!(m.undo(&mut s));
        assert_eq!(read_i64(&mut s), 2);
        assert!(!m.can_undo());
    }
    #[test]
    fn undo_manager_deep_history_round_trips() {
        let mut m = RefAnyUndoManager::new(0); // unlimited
        let mut s = state_i64(0);
        assert!(m.commit(&s));
        for v in 1..=100i64 {
            write_i64(&mut s, v);
            assert!(m.commit(&s));
        }
        assert_eq!(m.undo_diffs.len(), 100);
        for expected in (0..100i64).rev() {
            assert!(m.undo(&mut s));
            assert_eq!(read_i64(&mut s), expected);
        }
        assert!(!m.can_undo());
        assert!(m.can_redo());
        for expected in 1..=100i64 {
            assert!(m.redo(&mut s));
            assert_eq!(read_i64(&mut s), expected);
        }
        assert!(!m.can_redo());
        m.clear();
        assert!(!m.can_undo() && !m.can_redo());
        assert!(m.head.is_none());
        assert_eq!(read_i64(&mut s), 100, "clear() must not touch the state");
    }
    #[test]
    fn undo_manager_reports_false_when_the_restore_hook_fails() {
        // Serialization works (so commits land) but deserialization always
        // fails, so `undo` cannot write the state back: it must report `false`
        // instead of panicking or silently claiming success.
        let mut s = RefAny::new(1i64);
        s.set_serialize_fn(ser_i64 as usize);
        s.set_deserialize_fn(deser_always_err as usize);
        let mut m = RefAnyUndoManager::new(0);
        assert!(m.commit(&s));
        write_i64(&mut s, 2);
        assert!(m.commit(&s));
        assert!(!m.undo(&mut s), "restore failed -> undo must report false");
        assert_eq!(read_i64(&mut s), 2, "the state is left as it was");
        assert!(!m.can_undo());
    }
    #[test]
    fn undo_manager_round_trips_an_object_state_with_pointer_hostile_keys() {
        // The JSON key contains both '/' and '~', so the diff path only works if
        // esc()/unesc() are exact inverses (RFC 6901: ~1 then ~0, in that order).
        let mut s = RefAny::new(Doc { text: "hello".to_string(), cursor: 0 });
        s.set_serialize_fn(ser_doc as usize);
        s.set_deserialize_fn(deser_doc as usize);
        let mut m = RefAnyUndoManager::new(0);
        assert!(m.commit(&s));
        {
            let mut g = s.downcast_mut::<Doc>().expect("doc");
            g.text = "hello world".to_string();
            g.cursor = 11;
        }
        assert!(m.commit(&s));
        assert!(m.undo(&mut s));
        {
            let g = s.downcast_ref::<Doc>().expect("doc");
            assert_eq!(g.text, "hello");
            assert_eq!(g.cursor, 0);
        }
        assert!(m.redo(&mut s));
        let g = s.downcast_ref::<Doc>().expect("doc");
        assert_eq!(g.text, "hello world");
        assert_eq!(g.cursor, 11);
    }
    // ------------------------------------------------------------------
    // jsondiff::diff / apply — reversibility invariants
    // ------------------------------------------------------------------
    #[test]
    fn diff_of_identical_values_is_empty_and_apply_of_nothing_is_identity() {
        use serde_json::json;
        for v in [
            json!(null),
            json!(0),
            json!("x"),
            json!([1, 2]),
            json!({"a": {"b": [1]}}),
        ] {
            assert!(super::jsondiff::diff(&v, &v).is_empty());
            assert_eq!(super::jsondiff::apply(&v, &[], true), v);
            assert_eq!(super::jsondiff::apply(&v, &[], false), v);
        }
    }
    #[test]
    fn diff_apply_is_reversible_for_every_change_shape() {
        use serde_json::json;
        let cases = [
            (json!(1), json!(2)),                                      // scalar at the root
            (json!(null), json!({"a": 1})),                            // type change at the root
            (json!({"a": 1}), json!({"a": 1, "b": 2})),                // key added
            (json!({"a": 1, "b": 2}), json!({"a": 1})),                // key removed
            (json!({"a": {"b": {"c": 1}}}), json!({"a": {"b": {"c": 2}}})), // nested leaf
            (json!({"a": [1, 2]}), json!({"a": [2, 1]})),              // arrays are leaves
            (json!({"a": 1}), json!({"a": "1"})),                      // leaf type change
            (json!({"a": 1}), json!([1])),                             // object -> array
            (json!({}), json!({})),                                    // no-op
            (json!({"": 1}), json!({"": 2})),                          // empty key
        ];
        for (a, b) in &cases {
            let d = super::jsondiff::diff(a, b);
            assert_eq!(&super::jsondiff::apply(a, &d, true), b, "forward {a} -> {b}");
            assert_eq!(&super::jsondiff::apply(b, &d, false), a, "backward {b} -> {a}");
        }
    }
    #[test]
    fn diff_apply_handles_pointer_escapes_and_unicode_keys() {
        use serde_json::json;
        // Keys that collide with RFC-6901 pointer syntax, plus an empty key and
        // a multi-byte key (the path is sliced at a byte offset).
        let a = json!({
            "a/b": 1,
            "~": 2,
            "~0": 3,
            "~1": 4,
            "a~1b": 5,
            "": 6,
            "\u{1F600}": 7,
        });
        let mut b = a.clone();
        for (_k, v) in b.as_object_mut().expect("object").iter_mut() {
            *v = json!(0);
        }
        let d = super::jsondiff::diff(&a, &b);
        assert_eq!(d.len(), 7, "each key must yield exactly one change");
        assert_eq!(super::jsondiff::apply(&a, &d, true), b, "escaping is not round-tripping");
        assert_eq!(super::jsondiff::apply(&b, &d, false), a);
    }
    #[test]
    fn apply_ignores_changes_whose_path_cannot_be_resolved() {
        use serde_json::json;
        let base = json!({"a": 1});
        let changes = vec![
            // parent does not exist
            super::jsondiff::Change {
                path: "/x/y".to_string(),
                old: None,
                new: Some(json!(1)),
            },
            // not a JSON Pointer (no leading '/')
            super::jsondiff::Change {
                path: "a/b".to_string(),
                old: None,
                new: Some(json!(2)),
            },
            // root "removal" is a documented no-op
            super::jsondiff::Change { path: String::new(), old: None, new: None },
        ];
        assert_eq!(super::jsondiff::apply(&base, &changes, true), base);
        assert_eq!(super::jsondiff::apply(&base, &changes, false), base);
    }
    #[test]
    fn diff_of_a_large_object_is_reversible() {
        let mut a = serde_json::Map::new();
        let mut b = serde_json::Map::new();
        for i in 0..2_000i64 {
            a.insert(format!("k{i}"), serde_json::json!(i));
            b.insert(format!("k{i}"), serde_json::json!(i + 1));
        }
        let a = serde_json::Value::Object(a);
        let b = serde_json::Value::Object(b);
        let d = super::jsondiff::diff(&a, &b);
        assert_eq!(d.len(), 2_000);
        assert_eq!(super::jsondiff::apply(&a, &d, true), b);
        assert_eq!(super::jsondiff::apply(&b, &d, false), a);
    }
    #[test]
    fn diff_of_deeply_nested_objects_does_not_overflow() {
        fn nest(depth: usize, leaf: i64) -> serde_json::Value {
            let mut v = serde_json::json!(leaf);
            for _ in 0..depth {
                v = serde_json::json!({ "a": v });
            }
            v
        }
        let a = nest(200, 1);
        let b = nest(200, 2);
        let d = super::jsondiff::diff(&a, &b);
        assert_eq!(d.len(), 1, "only the leaf changed");
        assert_eq!(d[0].path, "/a".repeat(200));
        assert_eq!(super::jsondiff::apply(&a, &d, true), b);
        assert_eq!(super::jsondiff::apply(&b, &d, false), a);
    }
}