Skip to main content

xrpl_wasm_stdlib/core/
locator.rs

1//! Builder for nested field access locators.
2//!
3//! Locators encode a path to a nested field (sfields and array indices) in a compact
4//! binary format understood by the host. Use it to access fields like `Memos[0].MemoType`.
5//!
6//! Example
7//! ```no_run
8//! use xrpl_wasm_stdlib::core::locator::Locator;
9//! use xrpl_wasm_stdlib::sfield;
10//! let mut l = Locator::new();
11//! l.pack(sfield::Memos);
12//! l.pack(0);
13//! l.pack(sfield::MemoType);
14//! # let _ = (l.len() >= 3);
15//! ```
16
17/// The size of the buffer, in bytes, to use for any new locator
18const LOCATOR_BUFFER_SIZE: usize = 64; // max depth: 64/4 = 16
19
20/// A Locator encodes a path to a nested field as a sequence of 4-byte packed values
21/// (sfield codes or array indices) in a compact binary format understood by the host.
22///
23/// ## Derived Traits
24///
25/// - `Debug`: Useful for development and debugging
26/// - `Clone`: Reasonable for this 72-byte struct when explicit copying is needed
27/// - `Eq, PartialEq`: Enable comparisons between locators
28///
29/// Note: `Copy` is intentionally not derived due to the struct's size (72 bytes).
30/// Large `Copy` types can lead to accidental expensive copies and poor performance.
31/// Use `.clone()` when you need to duplicate a locator.
32#[derive(Clone, PartialEq, Eq, Debug)]
33#[repr(C)]
34pub struct Locator {
35    buffer: [u8; LOCATOR_BUFFER_SIZE],
36
37    /// An index into `buffer` where the next packing operation can be stored.
38    cur_buffer_index: usize,
39}
40
41impl Default for Locator {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl Locator {
48    /// Create a new empty Locator.
49    pub fn new() -> Locator {
50        Self {
51            buffer: [0; LOCATOR_BUFFER_SIZE],
52            cur_buffer_index: 0,
53        }
54    }
55
56    pub fn pack(&mut self, sfield_or_index: impl Into<i32>) -> bool {
57        if self.cur_buffer_index + 4 > LOCATOR_BUFFER_SIZE {
58            return false;
59        }
60
61        let value_bytes: [u8; 4] = sfield_or_index.into().to_le_bytes();
62        self.buffer[self.cur_buffer_index..self.cur_buffer_index + 4].copy_from_slice(&value_bytes);
63        self.cur_buffer_index += 4;
64
65        true
66    }
67
68    pub fn as_ptr(&self) -> *const u8 {
69        self.buffer.as_ptr()
70    }
71
72    pub fn num_packed_bytes(&self) -> usize {
73        self.cur_buffer_index
74    }
75
76    pub fn len(&self) -> usize {
77        self.cur_buffer_index
78    }
79
80    pub fn is_empty(&self) -> bool {
81        self.cur_buffer_index == 0
82    }
83
84    pub fn repack_last(&mut self, sfield_or_index: impl Into<i32>) -> bool {
85        if self.cur_buffer_index < 4 {
86            return false;
87        }
88
89        self.cur_buffer_index -= 4;
90
91        let value_bytes: [u8; 4] = sfield_or_index.into().to_le_bytes();
92        self.buffer[self.cur_buffer_index..self.cur_buffer_index + 4].copy_from_slice(&value_bytes);
93        self.cur_buffer_index += 4;
94
95        true
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::sfield;
103
104    #[test]
105    fn test_pack_with_sfield_no_into_needed() {
106        // This test demonstrates that .into() is no longer needed when using SField constants
107        let mut locator = Locator::new();
108
109        // Pack SField constants directly without .into()
110        assert!(locator.pack(sfield::Memos));
111        assert!(locator.pack(0));
112        assert!(locator.pack(sfield::MemoData));
113
114        assert_eq!(locator.len(), 12); // 3 packed values * 4 bytes each
115    }
116
117    #[test]
118    fn test_pack_with_i32_still_works() {
119        // This test verifies that i32 values still work as before
120        let mut locator = Locator::new();
121
122        assert!(locator.pack(123i32));
123        assert!(locator.pack(456i32));
124
125        assert_eq!(locator.len(), 8); // 2 packed values * 4 bytes each
126    }
127
128    #[test]
129    fn test_repack_last_with_sfield() {
130        let mut locator = Locator::new();
131
132        locator.pack(sfield::Memos);
133        locator.pack(0);
134
135        // Repack the last value with a different SField
136        assert!(locator.repack_last(sfield::MemoData));
137
138        assert_eq!(locator.len(), 8); // Still 2 packed values
139    }
140
141    #[test]
142    fn test_new_starts_empty() {
143        let locator = Locator::new();
144        assert_eq!(locator.len(), 0);
145        assert!(locator.is_empty());
146    }
147
148    #[test]
149    fn test_default_same_as_new() {
150        assert_eq!(Locator::default(), Locator::new());
151    }
152
153    #[test]
154    fn test_pack_writes_correct_bytes() {
155        let mut locator = Locator::new();
156        assert!(locator.pack(0x12345678i32));
157        assert_eq!(locator.len(), 4);
158
159        let bytes = unsafe { core::slice::from_raw_parts(locator.as_ptr(), 4) };
160        assert_eq!(bytes, &0x12345678i32.to_le_bytes());
161    }
162
163    #[test]
164    fn test_pack_returns_false_when_buffer_full() {
165        let mut locator = Locator::new();
166
167        // Fill all 16 slots (64 bytes / 4 bytes per pack)
168        for i in 0..16 {
169            assert!(locator.pack(i));
170        }
171        assert_eq!(locator.len(), 64);
172
173        // 17th pack should fail
174        assert!(!locator.pack(999i32));
175        assert_eq!(locator.len(), 64);
176    }
177
178    #[test]
179    fn test_is_empty_false_after_pack() {
180        let mut locator = Locator::new();
181        assert!(locator.is_empty());
182
183        locator.pack(sfield::Memos);
184        assert!(!locator.is_empty());
185        assert_eq!(locator.len(), 4);
186    }
187
188    #[test]
189    fn test_num_packed_bytes_equals_len() {
190        let mut locator = Locator::new();
191        assert_eq!(locator.num_packed_bytes(), locator.len());
192
193        locator.pack(sfield::Memos);
194        assert_eq!(locator.num_packed_bytes(), locator.len());
195        assert_eq!(locator.num_packed_bytes(), 4);
196
197        locator.pack(0);
198        assert_eq!(locator.num_packed_bytes(), locator.len());
199        assert_eq!(locator.num_packed_bytes(), 8);
200    }
201
202    #[test]
203    fn test_repack_last_on_empty_returns_false() {
204        let mut locator = Locator::new();
205        assert!(!locator.repack_last(sfield::Memos));
206        assert_eq!(locator.len(), 0);
207    }
208
209    #[test]
210    fn test_repack_last_overwrites_correct_bytes() {
211        let mut locator = Locator::new();
212        locator.pack(0x11111111i32);
213        locator.pack(0x22222222i32);
214        assert_eq!(locator.len(), 8);
215
216        assert!(locator.repack_last(0x33333333i32));
217        assert_eq!(locator.len(), 8);
218
219        let bytes = unsafe { core::slice::from_raw_parts(locator.as_ptr(), 8) };
220        // First value unchanged
221        assert_eq!(&bytes[0..4], &0x11111111i32.to_le_bytes());
222        // Second value replaced
223        assert_eq!(&bytes[4..8], &0x33333333i32.to_le_bytes());
224    }
225}