xrpl_common_stdlib/objects/traits.rs
1//! Generic ledger-object field accessor traits.
2//!
3//! The base traits `LedgerObjectCommonFields` / `CurrentLedgerObjectCommonFields` are
4//! hand-written here (no field is common to every ledger entry). Every per-ledger-entry
5//! field trait (`AccountRootFields`, `EscrowFields`, `OracleFields`, etc.) is generated in
6//! `crate::objects::generated` and re-exported from `crate::objects`; a couple are also
7//! re-exported here for a stable `objects::traits::*` import path.
8
9pub use crate::objects::generated::{AccountRootFields, EscrowFields};
10
11use crate::fields::locator::LedgerPathBuilder;
12use crate::host::Result;
13use crate::objects::{current_ledger_object, ledger_object};
14use crate::sfield;
15
16/// Trait providing access to common fields present in all ledger objects.
17///
18/// This trait defines methods to access standard fields that are common across
19/// different types of ledger objects in the XRP Ledger.
20pub trait LedgerObjectCommonFields {
21 // NOTE: `get_ledger_index()` is not in this trait because `sfLedgerIndex` is not actually a field on a ledger
22 // object (it's a synthetic field that maps to the `index` field, which is the unique ID of an object in the
23 // ledger's state tree). See https://github.com/XRPLF/rippled/issues/3649 for more context.
24
25 /// Returns the slot number (register number) where the ledger object is stored.
26 ///
27 /// This number is used to identify and access the specific ledger object
28 /// when retrieving or modifying its fields.
29 ///
30 /// # Returns
31 ///
32 /// The slot number as an i32 value
33 fn get_slot_num(&self) -> i32;
34
35 /// Starts a nested-field path rooted at this ledger object, read through its slot.
36 ///
37 /// Use this to reach into arrays and inner objects that the flat getters below can't return
38 /// whole (e.g. `SignerEntries[0].Account`). Chain [`field`](LedgerPathBuilder::field) /
39 /// [`index`](LedgerPathBuilder::index), then [`get::<T>()`](LedgerPathBuilder::get).
40 ///
41 /// ```no_run
42 /// use xrpl_common_stdlib::objects::traits::LedgerObjectCommonFields;
43 /// use xrpl_common_stdlib::sfield;
44 /// use xrpl_common_stdlib::types::account_id::AccountID;
45 /// # fn demo(obj: &impl LedgerObjectCommonFields) {
46 /// let signer = obj.path()
47 /// .field(sfield::SignerEntries)
48 /// .index(0)
49 /// .field(sfield::Account)
50 /// .get::<AccountID>();
51 /// # let _ = signer; }
52 /// ```
53 fn path(&self) -> LedgerPathBuilder {
54 LedgerPathBuilder::for_ledger_obj(self.get_slot_num())
55 }
56
57 /// Retrieves the flags field of the ledger object.
58 ///
59 /// # Arguments
60 ///
61 /// * `register_num` - The register number where the ledger object is stored
62 ///
63 /// # Returns
64 ///
65 /// The flags as a u32 value
66 fn get_flags(&self) -> Result<u32> {
67 ledger_object::get_field(self.get_slot_num(), sfield::Flags)
68 }
69
70 /// Retrieves the ledger entry type of the object.
71 ///
72 /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
73 ///
74 /// # Returns
75 ///
76 /// The ledger entry type as a u16 value
77 fn get_ledger_entry_type(&self) -> Result<u16> {
78 ledger_object::get_field(self.get_slot_num(), sfield::LedgerEntryType)
79 }
80}
81
82/// Trait providing access to common fields in the current ledger object.
83///
84/// This trait defines methods to access standard fields that are common across
85/// different types of ledger objects, specifically for the current ledger object
86/// being processed.
87pub trait CurrentLedgerObjectCommonFields {
88 // NOTE: `get_ledger_index()` is not in this trait because `sfLedgerIndex` is not actually a field on a ledger
89 // object (it's a synthetic field that maps to the `index` field, which is the unique ID of an object in the
90 // ledger's state tree). See https://github.com/XRPLF/rippled/issues/3649 for more context.
91
92 /// Starts a nested-field path rooted at the current ledger object (no slot).
93 ///
94 /// Use this to reach into arrays and inner objects that the flat getters below can't return
95 /// whole. Chain [`field`](LedgerPathBuilder::field) / [`index`](LedgerPathBuilder::index), then
96 /// [`get::<T>()`](LedgerPathBuilder::get).
97 ///
98 /// ```no_run
99 /// use xrpl_common_stdlib::objects::traits::CurrentLedgerObjectCommonFields;
100 /// use xrpl_common_stdlib::sfield;
101 /// use xrpl_common_stdlib::types::amount::Amount;
102 /// # fn demo(obj: &impl CurrentLedgerObjectCommonFields) {
103 /// let amount = obj.path().field(sfield::Amount).get::<Amount>();
104 /// # let _ = amount; }
105 /// ```
106 fn path(&self) -> LedgerPathBuilder {
107 LedgerPathBuilder::for_current_ledger_obj()
108 }
109
110 /// Retrieves the flags field of the current ledger object.
111 ///
112 /// # Returns
113 ///
114 /// The flags as a u32 value
115 fn get_flags(&self) -> Result<u32> {
116 current_ledger_object::get_field(sfield::Flags)
117 }
118
119 /// Retrieves the ledger entry type of the current ledger object.
120 ///
121 /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
122 ///
123 /// # Returns
124 ///
125 /// The ledger entry type as a u16 value
126 fn get_ledger_entry_type(&self) -> Result<u16> {
127 current_ledger_object::get_field(sfield::LedgerEntryType)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::fields::decoder::FromLedger;
135 use crate::host::error_codes::{INVALID_FIELD, SOME_ERROR};
136 use crate::host::host_bindings_trait::MockHostBindings;
137 use crate::objects::AccountRoot;
138 use crate::sfield::SField;
139 use mockall::predicate::{always, eq};
140
141 // ========================================
142 // Test helper functions
143 // ========================================
144
145 /// Helper to set up a mock expectation for home_le_field
146 ///
147 /// Sets up a mock expectation that will match calls with:
148 /// - field: The SField with the specified CODE
149 /// - size: The expected buffer size
150 /// - times: How many times this expectation should be matched
151 ///
152 /// When a test fails, mockall will show which parameter didn't match.
153 fn expect_current_field<
154 T: FromLedger + Send + std::fmt::Debug + PartialEq + 'static,
155 const CODE: i32,
156 >(
157 mock: &mut MockHostBindings,
158 _field: SField<T, CODE>,
159 size: usize,
160 times: usize,
161 ) {
162 mock.expect_home_le_field()
163 .with(eq(CODE), always(), eq(size))
164 .times(times)
165 .returning(move |_, _, _| size as i32);
166 }
167
168 /// Helper to set up a mock expectation for le_field
169 ///
170 /// Sets up a mock expectation that will match calls with:
171 /// - slot: The ledger object slot number
172 /// - field: The SField with the specified CODE
173 /// - size: The expected buffer size
174 /// - times: How many times this expectation should be matched
175 ///
176 /// When a test fails, mockall will show which parameter didn't match.
177 fn expect_ledger_field<
178 T: FromLedger + Send + std::fmt::Debug + PartialEq + 'static,
179 const CODE: i32,
180 >(
181 mock: &mut MockHostBindings,
182 slot: i32,
183 _field: SField<T, CODE>,
184 size: usize,
185 times: usize,
186 ) {
187 mock.expect_le_field()
188 .with(eq(slot), eq(CODE), always(), eq(size))
189 .times(times)
190 .returning(move |_, _, _, _| size as i32);
191 }
192
193 mod ledger_object_common_fields {
194 use super::*;
195 use crate::host::setup_mock;
196
197 #[test]
198 fn test_mandatory_fields_return_ok() {
199 let mut mock = MockHostBindings::new();
200
201 // get_flags
202 expect_ledger_field(&mut mock, 1, sfield::Flags, 4, 1);
203 // get_ledger_entry_type
204 expect_ledger_field(&mut mock, 1, sfield::LedgerEntryType, 2, 1);
205
206 let _guard = setup_mock(mock);
207
208 let account = AccountRoot::new(1);
209
210 // All mandatory fields should return Ok
211 assert!(account.get_flags().is_ok());
212 assert!(account.get_ledger_entry_type().is_ok());
213 }
214
215 #[test]
216 fn test_mandatory_fields_return_error_on_internal_error() {
217 let mut mock = MockHostBindings::new();
218
219 // get_flags with SOME_ERROR
220 mock.expect_le_field()
221 .with(eq(1), eq(sfield::Flags), always(), eq(4))
222 .times(1)
223 .returning(|_, _, _, _| SOME_ERROR);
224
225 let _guard = setup_mock(mock);
226
227 let account = AccountRoot::new(1);
228 let result = account.get_flags();
229
230 assert!(result.is_err());
231 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
232 }
233
234 #[test]
235 fn test_get_ledger_entry_type_returns_error_on_internal_error() {
236 let mut mock = MockHostBindings::new();
237
238 mock.expect_le_field()
239 .with(eq(1), eq(sfield::LedgerEntryType), always(), eq(2))
240 .times(1)
241 .returning(|_, _, _, _| SOME_ERROR);
242
243 let _guard = setup_mock(mock);
244
245 let account = AccountRoot::new(1);
246 let result = account.get_ledger_entry_type();
247
248 assert!(result.is_err());
249 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
250 }
251
252 #[test]
253 fn test_mandatory_fields_return_error_on_invalid_field() {
254 let mut mock = MockHostBindings::new();
255
256 // get_flags with INVALID_FIELD
257 mock.expect_le_field()
258 .with(eq(1), eq(sfield::Flags), always(), eq(4))
259 .times(1)
260 .returning(|_, _, _, _| INVALID_FIELD);
261
262 let _guard = setup_mock(mock);
263
264 let account = AccountRoot::new(1);
265 let result = account.get_flags();
266
267 assert!(result.is_err());
268 assert_eq!(result.err().unwrap().code(), INVALID_FIELD);
269 }
270 }
271
272 mod current_ledger_object_common_fields {
273 use super::*;
274 use crate::host::setup_mock;
275
276 struct TestCurrentLedgerObject;
277 impl CurrentLedgerObjectCommonFields for TestCurrentLedgerObject {}
278
279 #[test]
280 fn test_mandatory_fields_return_ok() {
281 let mut mock = MockHostBindings::new();
282
283 // get_flags
284 expect_current_field(&mut mock, sfield::Flags, 4, 1);
285 // get_ledger_entry_type
286 expect_current_field(&mut mock, sfield::LedgerEntryType, 2, 1);
287
288 let _guard = setup_mock(mock);
289
290 let escrow = TestCurrentLedgerObject;
291
292 // All mandatory fields should return Ok
293 assert!(escrow.get_flags().is_ok());
294 assert!(escrow.get_ledger_entry_type().is_ok());
295 }
296
297 #[test]
298 fn test_mandatory_fields_return_error_on_internal_error() {
299 let mut mock = MockHostBindings::new();
300
301 // get_flags with SOME_ERROR
302 mock.expect_home_le_field()
303 .with(eq(sfield::Flags), always(), eq(4))
304 .times(1)
305 .returning(|_, _, _| SOME_ERROR);
306
307 let _guard = setup_mock(mock);
308
309 let escrow = TestCurrentLedgerObject;
310 let result = escrow.get_flags();
311
312 assert!(result.is_err());
313 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
314 }
315
316 #[test]
317 fn test_get_ledger_entry_type_returns_error_on_internal_error() {
318 let mut mock = MockHostBindings::new();
319
320 mock.expect_home_le_field()
321 .with(eq(sfield::LedgerEntryType), always(), eq(2))
322 .times(1)
323 .returning(|_, _, _| SOME_ERROR);
324
325 let _guard = setup_mock(mock);
326
327 let escrow = TestCurrentLedgerObject;
328 let result = escrow.get_ledger_entry_type();
329
330 assert!(result.is_err());
331 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
332 }
333
334 #[test]
335 fn test_mandatory_fields_return_error_on_invalid_field() {
336 let mut mock = MockHostBindings::new();
337
338 // get_flags with INVALID_FIELD
339 mock.expect_home_le_field()
340 .with(eq(sfield::Flags), always(), eq(4))
341 .times(1)
342 .returning(|_, _, _| INVALID_FIELD);
343
344 let _guard = setup_mock(mock);
345
346 let escrow = TestCurrentLedgerObject;
347 let result = escrow.get_flags();
348
349 assert!(result.is_err());
350 assert_eq!(result.err().unwrap().code(), INVALID_FIELD);
351 }
352 }
353}