subgraph/ffi/
value.rs

1//! FFI-safe AssemblyScript dynamic values.
2
3use super::{
4    boxed::{AscBox, AscNullableBox, AscRef},
5    num::{AscBigDecimal, AscBigInt},
6    str::{AscStr, AscString},
7    types::{AscAddress, AscBytes},
8};
9use std::{
10    mem::{self, ManuallyDrop},
11    slice,
12};
13
14/// An array of AssemblyScript values.
15#[repr(C)]
16pub struct AscArray<T> {
17    buffer: AscBox<[T]>,
18    data_start: *const T,
19    byte_length: usize,
20    length: usize,
21}
22
23impl<T> AscArray<T> {
24    /// Creates a new AssemblyScript array from the specificed vector.
25    pub fn new(items: Vec<T>) -> AscBox<Self> {
26        let length = items.len();
27
28        let buffer = items.into_iter().collect::<AscBox<_>>();
29        let data_start = buffer.as_ptr().cast();
30        let byte_length = length * mem::size_of::<T>();
31
32        AscBox::new(Self {
33            buffer,
34            data_start,
35            byte_length,
36            length,
37        })
38    }
39
40    /// Returns the array as a slice.
41    pub fn as_slice(&self) -> &[T] {
42        // SAFETY: `data` points to an allocated value array of known length
43        // where all elements are initialized. Additionally, `AscObject<T>` is a
44        // transparent representation of a pointer to `T`.
45        unsafe { slice::from_raw_parts(self.data_start, self.length) }
46    }
47}
48
49impl<T> Clone for AscArray<T>
50where
51    T: Clone,
52{
53    fn clone(&self) -> Self {
54        let buffer = self.buffer.clone();
55        let data_start = buffer.as_ptr().cast();
56
57        Self {
58            buffer,
59            data_start,
60            byte_length: self.byte_length,
61            length: self.length,
62        }
63    }
64}
65
66/// A AssemblyScript map with string keys.
67#[repr(C)]
68pub struct AscMap<T> {
69    entries: AscBox<AscArray<AscBox<AscMapEntry<T>>>>,
70}
71
72/// An entry in a JSON object.
73#[repr(C)]
74pub struct AscMapEntry<T> {
75    key: AscString,
76    value: T,
77}
78
79impl<T> AscMap<T> {
80    /// Returns entries as a slice.
81    pub fn new(entries: Vec<AscBox<AscMapEntry<T>>>) -> AscBox<Self> {
82        AscBox::new(Self {
83            entries: AscArray::new(entries),
84        })
85    }
86
87    /// Returns entries as a slice.
88    pub fn entries(&self) -> &[AscBox<AscMapEntry<T>>] {
89        self.entries.as_asc_ref().as_slice()
90    }
91}
92
93impl<T> AscMapEntry<T> {
94    /// Creates a new AssemblyScript map entry.
95    pub fn new(key: AscString, value: T) -> AscBox<Self> {
96        AscBox::new(Self { key, value })
97    }
98
99    /// Returns the entry key.
100    pub fn key(&self) -> &AscStr {
101        self.key.as_asc_str()
102    }
103
104    /// Returns the entry value.
105    pub fn value(&self) -> &T {
106        &self.value
107    }
108}
109
110/// An AssemblyScript result type.
111#[repr(C)]
112pub struct AscResult<T, E> {
113    ok: AscNullableBox<T>,
114    err: AscNullableBox<E>,
115}
116
117impl<T, E> AscResult<T, E> {
118    /// Converst the AssemblyScript result wrapper into a Rust standard library
119    /// [`Result`].
120    pub fn as_std_result(&self) -> Result<&T, &E> {
121        match (self.ok.as_asc_ref(), self.err.as_asc_ref()) {
122            (Some(ok), None) => Ok(ok),
123            (None, Some(err)) => Err(err),
124            _ => panic!("inconsistent result"),
125        }
126    }
127}
128
129/// Generate code for a tagged union.
130macro_rules! asc_tagged_union {
131    (
132        $(#[$attr:meta])*
133        $value:ident, $kind:ident, $payload:ident, $data:ident {$(
134            $variant:ident , $field:ident ($($type:tt)*) = $tag:literal ,
135
136        )*}
137    ) => {
138        $(#[$attr])*
139        #[repr(C)]
140        pub struct $value {
141            kind: $kind,
142            data: $payload,
143        }
144
145        #[allow(clippy::manual_non_exhaustive, dead_code)]
146        #[derive(Clone, Copy, Debug)]
147        #[non_exhaustive]
148        #[repr(u32)]
149        enum $kind {
150            $(
151                $variant = $tag,
152            )*
153            #[doc(hidden)]
154            _NonExhaustive,
155        }
156
157        #[repr(C)]
158        union $payload {
159            $(
160                $field: asc_tagged_union_field!(field: $($type)*),
161            )*
162            _padding: u64,
163        }
164
165        pub enum $data<'a> {
166            $(
167                $variant(asc_tagged_union_field!(ref 'a: $($type)*)),
168            )*
169        }
170
171        #[allow(dead_code, unused_variables)]
172        impl $value {
173            $(
174                /// Creates a new value.
175                pub fn $field(
176                    value: asc_tagged_union_field!(owned: $($type)*),
177                ) -> AscBox<Self> {
178                    AscBox::new(Self {
179                        kind: $kind::$variant,
180                        data: $payload {
181                            $field: asc_tagged_union_field!(new(value): $($type)*),
182                        },
183                    })
184                }
185            )*
186
187            /// Returns a reference to the inner data for this value.
188            pub fn data(&self) -> $data {
189                match self.kind {
190                    $(
191                        $kind::$variant => $data::$variant(
192                            asc_tagged_union_field!(data(self.data.$field): $($type)*),
193                        ),
194                    )*
195                    _ => panic!("unknown value kind {:#x}", self.kind as u32),
196                }
197            }
198        }
199
200        impl Drop for $value {
201            fn drop(&mut self) {
202                // SAFETY: By construction, we are using the right union variant
203                // and we only ever drop when the container is dropping, meening
204                // the field will no longer be accessed.
205                match self.kind {
206                    $(
207                        $kind::$variant =>
208                            asc_tagged_union_field!(drop(self.data.$field): $($type)*),
209                    )*
210                    _ => ()
211                }
212            }
213        }
214    };
215}
216
217#[rustfmt::skip]
218macro_rules! asc_tagged_union_field {
219    (owned: null) => { () };
220    (ref $a:lifetime: null) => { () };
221    (field: null) => { u64 };
222    (new($f:expr): null) => { 0 };
223    (data($f:expr): null) => { () };
224    (drop($f:expr): null) => { () };
225
226    (owned: string) => { AscString };
227    (ref $a:lifetime: string) => { &$a AscStr };
228    (field: string) => { ManuallyDrop<AscString> };
229    (new($f:expr): string) => { ManuallyDrop::new($f) };
230    (data($f:expr): string) => { unsafe { $f.as_asc_str() } };
231    (drop($f:expr): string) => { unsafe { ManuallyDrop::drop(&mut $f) } };
232
233    (owned: value $type:ty) => { $type };
234    (ref $a:lifetime: value $type:ty) => { $type };
235    (field: value $type:ty) => { $type };
236    (new($f:expr): value $type:ty) => { $f };
237    (data($f:expr): value $type:ty) => { unsafe { $f } };
238    (drop($f:expr): value $type:ty) => { () };
239
240    (owned: boxed $type:ty) => { AscBox<$type> };
241    (ref $a:lifetime: boxed $type:ty) => { &$a AscRef<$type> };
242    (field: boxed $type:ty) => { ManuallyDrop<AscBox<$type>> };
243    (new($f:expr): boxed $type:ty) => { ManuallyDrop::new($f) };
244    (data($f:expr): boxed $type:ty) => { unsafe { $f.as_asc_ref() } };
245    (drop($f:expr): boxed $type:ty) => { unsafe { ManuallyDrop::drop(&mut $f) } };
246}
247
248/// An AssemblyScript Subgraph key-value map.
249pub type AscEntity = AscMap<AscBox<AscEntityValue>>;
250
251asc_tagged_union! {
252    /// An AssemblyScript JSON dynamic value.
253    AscEntityValue,
254    AscEntityValueKind,
255    AscEntityValuePayload,
256    AscEntityValueData {
257        String, string (string) = 0,
258        Int, int (value i32) = 1,
259        BigDecimal, bigdecimal (boxed AscBigDecimal) = 2,
260        Bool, bool (value bool) = 3,
261        Array, array (boxed AscArray<AscBox<AscEntityValue>>) = 4,
262        Null, null (null) = 5,
263        Bytes, bytes (boxed AscBytes) = 6,
264        BigInt, bigint (boxed AscBigInt) = 7,
265    }
266}
267
268asc_tagged_union! {
269    /// An AssemblyScript JSON dynamic value.
270    AscJsonValue,
271    AscJsonValueKind,
272    AscJsonValuePayload,
273    AscJsonValueData {
274        Null, null (null) = 0,
275        Bool, bool (value bool) = 1,
276        Number, number (string) = 2,
277        String, string (string) = 3,
278        Array, array (boxed AscArray<AscBox<AscJsonValue>>) = 4,
279        Object, object (boxed AscMap<AscBox<AscJsonValue>>) = 5,
280    }
281}
282
283asc_tagged_union! {
284    /// An AssemblyScript Ethereum dynamic value.
285    AscEthereumValue,
286    AscEthereumValueKind,
287    AscEthereumValuePayload,
288    AscEthereumValueData {
289        Address, address (boxed AscAddress) = 0,
290        FixedBytes, fixedbytes (boxed AscBytes) = 1,
291        Bytes, bytes (boxed AscBytes) = 2,
292        Int, int (boxed AscBigInt) = 3,
293        Uint, uint (boxed AscBigInt) = 4,
294        Bool, bool (value bool) = 5,
295        String, string (string) = 6,
296        FixedArray, fixedarray (boxed AscArray<AscBox<AscEthereumValue>>) = 7,
297        Array, array (boxed AscArray<AscBox<AscEthereumValue>>) = 8,
298        Tuple, tuple (boxed AscArray<AscBox<AscEthereumValue>>) = 9,
299    }
300}