Skip to main content

iota_sdk_move_types/
macros.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Internal macros generating the `Object` constructors that every `key`
5//! Move-object mirror shares.
6//!
7//! The mirrors differ only in their type and (for generic mirrors) their
8//! single type parameter, so the `TryFrom<&Object>` /
9//! `try_from_object_with_type` bodies are otherwise identical boilerplate.
10//! `from_bcs` and the field accessors stay hand-written per type.
11//!
12//! The [`StructTag`] predicate a mirror validates against is derived from its
13//! name as `is_<name:snake>` — the same `paste` snake-casing that generated
14//! that predicate in the first place, applied to the same identifier, so it
15//! always matches for a type registered on a plain `add_struct_tag_ctor!` arm.
16//! A type whose predicate lives under a different name — one registered with
17//! `@with_module` (predicate `is_<module>_<name>`), or a mirror whose Rust name
18//! diverges from the Move struct name — passes its predicate explicitly as a
19//! second argument.
20//!
21//! [`StructTag`]: iota_types::StructTag
22
23/// Generate the `TryFrom<&Object>` constructor for a non-generic mirror.
24///
25/// The predicate defaults to `is_<TypeName:snake>`; pass it explicitly as a
26/// second argument when it lives under a different name (e.g. an
27/// `@with_module` registration, whose predicate is `is_<module>_<name>`).
28macro_rules! impl_try_from_object {
29    ($ty:ident, $is_fn:ident $(,)?) => {
30        #[cfg(feature = "serde")]
31        #[doc = concat!(
32            "Decode a [`",
33            stringify!($ty),
34            "`] from an on-chain object, validating that the object's Move type tag matches."
35        )]
36        impl TryFrom<&::iota_types::Object> for $ty {
37            type Error = $crate::FromObjectError;
38
39            fn try_from(object: &::iota_types::Object) -> Result<Self, Self::Error> {
40                let move_struct = object
41                    .as_opt_struct()
42                    .ok_or($crate::FromObjectError::NotAMoveStruct)?;
43                if !move_struct.object_type().$is_fn() {
44                    return Err($crate::FromObjectError::WrongType);
45                }
46                ::bcs::from_bytes(move_struct.contents()).map_err($crate::FromObjectError::Bcs)
47            }
48        }
49    };
50    ($ty:ident $(,)?) => {
51        ::paste::paste! {
52            impl_try_from_object!($ty, [< is_ $ty:snake >]);
53        }
54    };
55}
56
57/// Generate `try_from_object_with_type` and the `TryFrom<&Object>` constructor
58/// for a mirror with a single type parameter.
59///
60/// The predicate defaults to `is_<TypeName:snake>`; pass it explicitly as a
61/// second argument when it lives under a different name (e.g. an
62/// `@with_module` registration, whose predicate is `is_<module>_<name>`).
63macro_rules! impl_try_from_object_generic {
64    ($ty:ident<$param:ident>, $is_fn:ident $(,)?) => {
65        #[cfg(feature = "serde")]
66        impl<$param> $ty<$param>
67        where
68            $param: ::serde::de::DeserializeOwned,
69        {
70            #[doc = concat!(
71                "Decode a [`",
72                stringify!($ty),
73                "`] from an on-chain object, validating its Move type tag and that its type parameter equals `type_param`.\n\nEscape hatch for type parameters only known at runtime; nothing ties `type_param` to `",
74                stringify!($param),
75                "`. Prefer the `TryFrom` impl when the type parameter is known at compile time."
76            )]
77            pub fn try_from_object_with_type(
78                object: &::iota_types::Object,
79                type_param: &::iota_types::TypeTag,
80            ) -> Result<Self, $crate::FromObjectError> {
81                let move_struct = object
82                    .as_opt_struct()
83                    .ok_or($crate::FromObjectError::NotAMoveStruct)?;
84                let tag = move_struct.struct_tag();
85                if !tag.$is_fn() || tag.type_params() != ::core::slice::from_ref(type_param) {
86                    return Err($crate::FromObjectError::WrongType);
87                }
88                ::bcs::from_bytes(move_struct.contents()).map_err($crate::FromObjectError::Bcs)
89            }
90        }
91
92        #[cfg(feature = "serde")]
93        #[doc = concat!(
94            "Decode a [`",
95            stringify!($ty),
96            "`] from an on-chain object, validating its full Move type tag including the type parameter."
97        )]
98        impl<$param> TryFrom<&::iota_types::Object> for $ty<$param>
99        where
100            $param: ::serde::de::DeserializeOwned + $crate::MoveType,
101        {
102            type Error = $crate::FromObjectError;
103
104            fn try_from(object: &::iota_types::Object) -> Result<Self, Self::Error> {
105                Self::try_from_object_with_type(object, &<$param as $crate::MoveType>::type_tag())
106            }
107        }
108    };
109    ($ty:ident<$param:ident> $(,)?) => {
110        ::paste::paste! {
111            impl_try_from_object_generic!($ty<$param>, [< is_ $ty:snake >]);
112        }
113    };
114}