Skip to main content

radixdb_plugin/
value.rs

1use std::cmp::Ordering;
2
3use radixdb_plugin_abi::{
4    RadixAbiCodecFnV1, RadixAbiCompareFnV1, RadixAbiEqualFnV1, RadixAbiHashFnV1, RadixAbiParseFnV1,
5    RadixAbiTypeRefV1, RadixAbiValueV1, RADIX_BUILTIN_BOOLEAN, RADIX_BUILTIN_BYTES,
6    RADIX_BUILTIN_FLOAT, RADIX_BUILTIN_INTEGER, RADIX_BUILTIN_TEXT, RADIX_TYPE_REF_EXTERNAL,
7    RADIX_VALUE_FLAG_NULL,
8};
9
10use crate::{CodecReader, CodecWriter, HashSink, PluginError, PluginResult};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct BoundedBytes<const MAX: usize>(Vec<u8>);
14
15impl<const MAX: usize> BoundedBytes<MAX> {
16    pub fn new(bytes: impl Into<Vec<u8>>) -> PluginResult<Self> {
17        let bytes = bytes.into();
18        if bytes.len() > MAX {
19            return Err(PluginError::limit_exceeded(
20                "byte value exceeds its declared bound",
21            ));
22        }
23        Ok(Self(bytes))
24    }
25
26    pub fn as_slice(&self) -> &[u8] {
27        &self.0
28    }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct BoundedText<const MAX: usize>(String);
33
34impl<const MAX: usize> BoundedText<MAX> {
35    pub fn new(text: impl Into<String>) -> PluginResult<Self> {
36        let text = text.into();
37        if text.len() > MAX {
38            return Err(PluginError::limit_exceeded(
39                "text value exceeds its declared bound",
40            ));
41        }
42        Ok(Self(text))
43    }
44
45    pub fn as_str(&self) -> &str {
46        &self.0
47    }
48}
49
50pub trait RadixType: Sized + Send + Sync + 'static {
51    const LOCAL_ID: &'static str;
52    const DISPLAY_NAME: &'static str;
53    const CODEC_VERSION: u32;
54    const SEMANTIC_REVISION: u32;
55    const STORAGE_KIND: u16;
56    const FIXED_BYTES: u32;
57    const MAX_BYTES: u32;
58    const CAPABILITIES: u64;
59    const CODEC_FINGERPRINT: [u8; 32];
60
61    #[doc(hidden)]
62    const ABI_ENCODE: Option<RadixAbiCodecFnV1>;
63    #[doc(hidden)]
64    const ABI_DECODE: Option<RadixAbiParseFnV1>;
65    #[doc(hidden)]
66    const ABI_EQUALITY: Option<RadixAbiEqualFnV1>;
67    #[doc(hidden)]
68    const ABI_HASH: Option<RadixAbiHashFnV1>;
69    #[doc(hidden)]
70    const ABI_ORDERING: Option<RadixAbiCompareFnV1>;
71
72    fn encode(&self, output: &mut CodecWriter) -> PluginResult<()>;
73    fn decode(input: &mut CodecReader<'_>) -> PluginResult<Self>;
74    fn test_corpus() -> Vec<Self>;
75
76    fn semantic_equal(_left: &Self, _right: &Self) -> Option<bool> {
77        None
78    }
79
80    fn semantic_hash(_value: &Self, _sink: &mut HashSink<'_>) -> Option<PluginResult<()>> {
81        None
82    }
83
84    fn semantic_compare(_left: &Self, _right: &Self) -> Option<Ordering> {
85        None
86    }
87}
88
89pub trait ValueType: Sized {
90    const TYPE_REF: RadixAbiTypeRefV1;
91    const MAX_OUTPUT_BYTES: u32;
92
93    #[doc(hidden)]
94    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self>;
95    #[doc(hidden)]
96    fn encode_abi(&self) -> PluginResult<Vec<u8>>;
97
98    #[doc(hidden)]
99    fn is_null(&self) -> bool {
100        false
101    }
102}
103
104#[doc(hidden)]
105pub struct AbiValue<'a> {
106    raw: &'a RadixAbiValueV1,
107    bytes: &'a [u8],
108}
109
110impl<'a> AbiValue<'a> {
111    pub(crate) unsafe fn new(raw: &'a RadixAbiValueV1) -> PluginResult<Self> {
112        if raw.reserved != 0 || raw.flags & !RADIX_VALUE_FLAG_NULL != 0 {
113            return Err(PluginError::invalid_input("invalid ABI value flags"));
114        }
115        let bytes = if raw.borrowed_bytes.len == 0 {
116            &[]
117        } else {
118            if raw.borrowed_bytes.ptr.is_null() || raw.borrowed_bytes.reserved != 0 {
119                return Err(PluginError::invalid_input("invalid ABI value slice"));
120            }
121            // SAFETY: the host owns this call-scoped range and the ABI contract
122            // requires it to remain readable for the duration of the callback.
123            unsafe {
124                std::slice::from_raw_parts(raw.borrowed_bytes.ptr, raw.borrowed_bytes.len as usize)
125            }
126        };
127        Ok(Self { raw, bytes })
128    }
129
130    pub fn is_null(&self) -> bool {
131        self.raw.flags & RADIX_VALUE_FLAG_NULL != 0
132    }
133
134    fn expect_type(&self, expected: RadixAbiTypeRefV1) -> PluginResult<()> {
135        if self.raw.type_ref != expected {
136            return Err(PluginError::invalid_input("ABI value type mismatch"));
137        }
138        Ok(())
139    }
140}
141
142macro_rules! integer_value {
143    ($type:ty) => {
144        impl ValueType for $type {
145            const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_INTEGER);
146            const MAX_OUTPUT_BYTES: u32 = 8;
147
148            fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
149                value.expect_type(Self::TYPE_REF)?;
150                if value.is_null() {
151                    return Err(PluginError::invalid_input("unexpected NULL argument"));
152                }
153                let raw = i64::from_le_bytes(value.raw.inline_bytes[..8].try_into().unwrap());
154                <$type>::try_from(raw)
155                    .map_err(|_| PluginError::invalid_input("integer argument is out of range"))
156            }
157
158            fn encode_abi(&self) -> PluginResult<Vec<u8>> {
159                let value = i64::try_from(*self)
160                    .map_err(|_| PluginError::domain("integer result is out of ABI range"))?;
161                Ok(value.to_le_bytes().to_vec())
162            }
163        }
164    };
165}
166
167integer_value!(i8);
168integer_value!(i16);
169integer_value!(i32);
170integer_value!(i64);
171integer_value!(u8);
172integer_value!(u16);
173integer_value!(u32);
174
175impl ValueType for u64 {
176    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_INTEGER);
177    const MAX_OUTPUT_BYTES: u32 = 8;
178
179    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
180        value.expect_type(Self::TYPE_REF)?;
181        if value.is_null() {
182            return Err(PluginError::invalid_input("unexpected NULL argument"));
183        }
184        let raw = i64::from_le_bytes(value.raw.inline_bytes[..8].try_into().unwrap());
185        Self::try_from(raw).map_err(|_| PluginError::invalid_input("negative integer for u64"))
186    }
187
188    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
189        let value = i64::try_from(*self)
190            .map_err(|_| PluginError::domain("u64 result exceeds signed ABI integer"))?;
191        Ok(value.to_le_bytes().to_vec())
192    }
193}
194
195impl ValueType for f64 {
196    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_FLOAT);
197    const MAX_OUTPUT_BYTES: u32 = 8;
198
199    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
200        value.expect_type(Self::TYPE_REF)?;
201        if value.is_null() {
202            return Err(PluginError::invalid_input("unexpected NULL argument"));
203        }
204        Ok(Self::from_bits(u64::from_le_bytes(
205            value.raw.inline_bytes[..8].try_into().unwrap(),
206        )))
207    }
208
209    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
210        Ok(self.to_bits().to_le_bytes().to_vec())
211    }
212}
213
214impl ValueType for f32 {
215    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_FLOAT);
216    const MAX_OUTPUT_BYTES: u32 = 8;
217
218    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
219        Ok(f64::decode_abi(value)? as f32)
220    }
221
222    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
223        (*self as f64).encode_abi()
224    }
225}
226
227impl ValueType for bool {
228    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_BOOLEAN);
229    const MAX_OUTPUT_BYTES: u32 = 1;
230
231    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
232        value.expect_type(Self::TYPE_REF)?;
233        if value.is_null() {
234            return Err(PluginError::invalid_input("unexpected NULL argument"));
235        }
236        match value.raw.inline_bytes[0] {
237            0 => Ok(false),
238            1 => Ok(true),
239            _ => Err(PluginError::invalid_input("invalid ABI boolean")),
240        }
241    }
242
243    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
244        Ok(vec![u8::from(*self)])
245    }
246}
247
248impl<const MAX: usize> ValueType for BoundedBytes<MAX> {
249    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_BYTES);
250    const MAX_OUTPUT_BYTES: u32 = if MAX > u32::MAX as usize {
251        u32::MAX
252    } else {
253        MAX as u32
254    };
255
256    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
257        value.expect_type(Self::TYPE_REF)?;
258        if value.is_null() {
259            return Err(PluginError::invalid_input("unexpected NULL argument"));
260        }
261        Self::new(value.bytes)
262    }
263
264    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
265        Ok(self.0.clone())
266    }
267}
268
269impl<const MAX: usize> ValueType for BoundedText<MAX> {
270    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1::builtin(RADIX_BUILTIN_TEXT);
271    const MAX_OUTPUT_BYTES: u32 = if MAX > u32::MAX as usize {
272        u32::MAX
273    } else {
274        MAX as u32
275    };
276
277    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
278        value.expect_type(Self::TYPE_REF)?;
279        if value.is_null() {
280            return Err(PluginError::invalid_input("unexpected NULL argument"));
281        }
282        let text = std::str::from_utf8(value.bytes)
283            .map_err(|_| PluginError::invalid_input("text argument is not UTF-8"))?;
284        Self::new(text)
285    }
286
287    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
288        Ok(self.0.as_bytes().to_vec())
289    }
290}
291
292impl<T: RadixType> ValueType for T {
293    const TYPE_REF: RadixAbiTypeRefV1 = RadixAbiTypeRefV1 {
294        kind: RADIX_TYPE_REF_EXTERNAL,
295        builtin_tag: 0,
296        codec_version: T::CODEC_VERSION,
297        object_id: [0; 16],
298    };
299    const MAX_OUTPUT_BYTES: u32 = T::MAX_BYTES;
300
301    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
302        if value.raw.type_ref.kind != RADIX_TYPE_REF_EXTERNAL
303            || value.raw.type_ref.codec_version != T::CODEC_VERSION
304            || value.is_null()
305        {
306            return Err(PluginError::invalid_input(
307                "external ABI value type or codec mismatch",
308            ));
309        }
310        if T::STORAGE_KIND == radixdb_plugin_abi::RADIX_EXTERNAL_STORAGE_FIXED
311            && value.bytes.len() != T::FIXED_BYTES as usize
312        {
313            return Err(PluginError::invalid_input(
314                "fixed external value has wrong encoded width",
315            ));
316        }
317        let mut reader = CodecReader::new(value.bytes);
318        let decoded = T::decode(&mut reader)?;
319        reader.finish()?;
320        Ok(decoded)
321    }
322
323    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
324        let mut output = CodecWriter::new(T::MAX_BYTES as usize);
325        self.encode(&mut output)?;
326        let bytes = output.into_bytes();
327        if T::STORAGE_KIND == radixdb_plugin_abi::RADIX_EXTERNAL_STORAGE_FIXED
328            && bytes.len() != T::FIXED_BYTES as usize
329        {
330            return Err(PluginError::internal(
331                "fixed external codec emitted the wrong width",
332            ));
333        }
334        Ok(bytes)
335    }
336}
337
338impl<T: ValueType> ValueType for Option<T> {
339    const TYPE_REF: RadixAbiTypeRefV1 = T::TYPE_REF;
340    const MAX_OUTPUT_BYTES: u32 = T::MAX_OUTPUT_BYTES;
341
342    fn decode_abi(value: &AbiValue<'_>) -> PluginResult<Self> {
343        if value.is_null() {
344            Ok(None)
345        } else {
346            T::decode_abi(value).map(Some)
347        }
348    }
349
350    fn encode_abi(&self) -> PluginResult<Vec<u8>> {
351        match self {
352            Some(value) => value.encode_abi(),
353            None => Ok(Vec::new()),
354        }
355    }
356
357    fn is_null(&self) -> bool {
358        self.is_none()
359    }
360}