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}