xrpl_wasm_stdlib/fields/decoder.rs
1//! Field decoding traits for XRPL transaction and ledger object fields.
2//!
3//! This module defines a lightweight, host-independent decoding contract for types that
4//! are constructed directly from a raw byte buffer.
5//!
6//! # Examples
7//!
8//! ```
9//! use xrpl_wasm_stdlib::fields::decoder::{FieldDecoder, FromCurrentTx};
10//! use xrpl_wasm_stdlib::host::Error;
11//!
12//! /// A toy field: a single boolean flag byte (0 or 1).
13//! struct Flag(bool);
14//!
15//! impl FieldDecoder for Flag {
16//! type Buffer = [u8; 1];
17//!
18//! fn decode(bytes: &[u8]) -> Result<Self, Error> {
19//! match bytes {
20//! [0] => Ok(Flag(false)),
21//! [1] => Ok(Flag(true)),
22//! _ => Err(Error::InvalidDecoding),
23//! }
24//! }
25//! }
26//!
27//! // Declares that `Flag` can be decoded from the currently executing transaction.
28//! // `FromLedger` is implemented the same way for fields readable from ledger objects.
29//! impl FromCurrentTx for Flag {}
30//!
31//! let flag = Flag::decode(&[1]).unwrap();
32//! assert!(flag.0);
33//!
34//! assert!(Flag::decode(&[2]).is_err());
35//! ```
36
37use crate::host::Error;
38
39/// Decodes a fixed-format XRPL field from its raw byte representation.
40pub trait FieldDecoder: Sized {
41 /// A stack buffer sized to hold this field's raw bytes, used by generic host-field
42 /// getters to read into before calling [`decode`](FieldDecoder::decode).
43 type Buffer: AsMut<[u8]> + Default;
44
45 /// Decodes `Self` from `bytes`, returning an error if the bytes are malformed.
46 fn decode(bytes: &[u8]) -> Result<Self, Error>;
47}
48
49/// Marker trait for fields that can be decoded from the currently executing transaction.
50pub trait FromCurrentTx: FieldDecoder {}
51
52/// Marker trait for fields that can be decoded from a ledger object.
53pub trait FromLedger: FieldDecoder {}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[derive(Debug, PartialEq, Eq)]
60 struct TxOnly(u8);
61
62 impl FieldDecoder for TxOnly {
63 type Buffer = [u8; 1];
64
65 fn decode(bytes: &[u8]) -> Result<Self, Error> {
66 bytes
67 .first()
68 .copied()
69 .map(TxOnly)
70 .ok_or(Error::FieldNotFound)
71 }
72 }
73 impl FromCurrentTx for TxOnly {}
74
75 #[derive(Debug, PartialEq, Eq)]
76 struct ObjOnly(u8);
77
78 impl FieldDecoder for ObjOnly {
79 type Buffer = [u8; 1];
80
81 fn decode(bytes: &[u8]) -> Result<Self, Error> {
82 bytes
83 .first()
84 .copied()
85 .map(ObjOnly)
86 .ok_or(Error::FieldNotFound)
87 }
88 }
89 impl FromLedger for ObjOnly {}
90
91 #[derive(Debug, PartialEq, Eq)]
92 struct TxAndObj(u8);
93
94 impl FieldDecoder for TxAndObj {
95 type Buffer = [u8; 1];
96
97 fn decode(bytes: &[u8]) -> Result<Self, Error> {
98 bytes
99 .first()
100 .copied()
101 .map(TxAndObj)
102 .ok_or(Error::FieldNotFound)
103 }
104 }
105 impl FromCurrentTx for TxAndObj {}
106 impl FromLedger for TxAndObj {}
107
108 // These take no arguments and are never called; if a type didn't actually implement
109 // the trait, the crate would fail to compile.
110 fn assert_from_current_tx<T: FromCurrentTx>() {}
111 fn assert_from_ledger<T: FromLedger>() {}
112
113 #[test]
114 fn tx_only_implements_from_current_tx_only() {
115 assert_from_current_tx::<TxOnly>();
116 }
117
118 #[test]
119 fn obj_only_implements_from_ledger_only() {
120 assert_from_ledger::<ObjOnly>();
121 }
122
123 #[test]
124 fn tx_and_obj_implements_both() {
125 assert_from_current_tx::<TxAndObj>();
126 assert_from_ledger::<TxAndObj>();
127 }
128
129 #[test]
130 fn decode_returns_value_on_success() {
131 let decoded = TxOnly::decode(&[42]).unwrap();
132 assert_eq!(decoded, TxOnly(42));
133 }
134
135 #[test]
136 fn decode_returns_error_on_empty_input() {
137 let result = TxOnly::decode(&[]);
138 assert!(result.is_err());
139 assert_eq!(result.unwrap_err().code(), Error::FieldNotFound.code());
140 }
141
142 #[test]
143 fn buffer_type_has_expected_length() {
144 let mut buffer = <TxOnly as FieldDecoder>::Buffer::default();
145 assert_eq!(buffer.as_mut().len(), 1);
146 }
147}