Skip to main content

xrpl_common_stdlib/objects/
any_object.rs

1//! Untyped handle to a slot-cached ledger object.
2//!
3//! Typed wrappers like [`AccountRoot`](crate::objects::account_root::AccountRoot) exist to add
4//! object-specific named accessors (`AccountFields`, `EscrowFields`). Object types that have no such
5//! wrapper — Oracle, SignerList, NFTokenPage, RippleState — still need the common fields and, above
6//! all, inner-field paths. [`LedgerObject`] is that door: wrap the raw slot
7//! [`cache_le`](crate::objects::cache_le) handed back and get everything on
8//! [`LedgerObjectCommonFields`](crate::objects::traits::LedgerObjectCommonFields), including
9//! [`path()`](crate::objects::traits::LedgerObjectCommonFields::path).
10//!
11//! Adding a typed wrapper for an object later is purely additive — code written against
12//! `LedgerObject` keeps working.
13
14use crate::objects::traits::LedgerObjectCommonFields;
15
16/// A ledger object identified only by the slot it was cached into.
17///
18/// ```no_run
19/// use xrpl_common_stdlib::objects::LedgerObject;
20/// use xrpl_common_stdlib::objects::traits::LedgerObjectCommonFields;
21/// use xrpl_common_stdlib::sfield;
22/// # fn demo(slot: i32) {
23/// // Read PriceDataSeries[0].AssetPrice off an Oracle object, which has no typed wrapper.
24/// let price = LedgerObject::new(slot)
25///     .path()
26///     .field(sfield::PriceDataSeries)
27///     .index(0)
28///     .field(sfield::AssetPrice)
29///     .get::<u64>();
30/// # let _ = price; }
31/// ```
32#[derive(Debug, Clone, Copy, Eq, PartialEq)]
33pub struct LedgerObject {
34    pub slot_num: i32,
35}
36
37impl LedgerObject {
38    /// Wrap a slot returned by [`cache_le`](crate::objects::cache_le).
39    ///
40    /// The slot is not validated here — a negative slot means the caching call failed, and the
41    /// caller is expected to have checked that before building a handle. Field reads through an
42    /// invalid slot surface the host's error as usual.
43    pub fn new(slot_num: i32) -> Self {
44        Self { slot_num }
45    }
46}
47
48impl LedgerObjectCommonFields for LedgerObject {
49    fn get_slot_num(&self) -> i32 {
50        self.slot_num
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::host::host_bindings_trait::MockHostBindings;
58    use crate::host::setup_mock;
59    use crate::sfield;
60    use mockall::predicate::{always, eq};
61
62    #[test]
63    fn test_new_stores_slot() {
64        assert_eq!(LedgerObject::new(7).get_slot_num(), 7);
65    }
66
67    #[test]
68    fn test_inherits_common_fields() {
69        let mut mock = MockHostBindings::new();
70        mock.expect_le_field()
71            .with(eq(3), eq(sfield::Flags), always(), eq(4))
72            .times(1)
73            .returning(|_, _, _, _| 4);
74        let _guard = setup_mock(mock);
75
76        assert!(LedgerObject::new(3).get_flags().is_ok());
77    }
78
79    #[test]
80    fn test_path_reads_inner_field_through_its_slot() {
81        // The whole point of this type: an inner read on an object with no typed wrapper.
82        // PriceDataSeries[0].AssetPrice is three 4-byte segments = 12 bytes; u64 buffer is 8.
83        let mut mock = MockHostBindings::new();
84        mock.expect_le_inner()
85            .with(eq(9), always(), eq(12usize), always(), eq(8usize))
86            .times(1)
87            .returning(|_, _, _, _, _| 8);
88        let _guard = setup_mock(mock);
89
90        let result = LedgerObject::new(9)
91            .path()
92            .field(sfield::PriceDataSeries)
93            .index(0)
94            .field(sfield::AssetPrice)
95            .get::<u64>();
96
97        assert!(result.is_ok());
98    }
99}