hdk_derive/
lib.rs

1#![crate_type = "proc-macro"]
2
3use proc_macro::TokenStream;
4use proc_macro_error::abort;
5use proc_macro_error::abort_call_site;
6use proc_macro_error::proc_macro_error;
7use syn::spanned::Spanned;
8use util::get_return_type_ident;
9use util::is_extern_result_callback_result;
10
11mod dna_properties;
12mod entry_helper;
13mod entry_type_registration;
14mod entry_types;
15mod entry_types_conversions;
16mod entry_types_name_registration;
17mod entry_zomes;
18mod link_types;
19mod link_zomes;
20mod to_coordinates;
21mod unit_enum;
22mod util;
23
24#[proc_macro_error]
25#[proc_macro_attribute]
26pub fn hdk_extern(attrs: TokenStream, item: TokenStream) -> TokenStream {
27    // extern mapping is only valid for functions
28    let mut item_fn = syn::parse_macro_input!(item as syn::ItemFn);
29
30    let fn_name = item_fn.sig.ident.to_string();
31    let is_infallible = attrs.to_string() == "infallible";
32
33    // Check return type
34    if let syn::ReturnType::Type(_, ref ty) = item_fn.sig.output {
35        const EXTERN_RESULT: &str = "ExternResult";
36        const VALIDATE_CALLBACK_RESULT: &str = "ValidateCallbackResult";
37        const INIT_CALLBACK_RESULT: &str = "InitCallbackResult";
38
39        match (fn_name.as_str(), get_return_type_ident(ty)) {
40            ("validate" | "genesis_self_check", Some(return_type)) => {
41                if is_infallible && return_type != VALIDATE_CALLBACK_RESULT {
42                    abort!(
43                        ty.span(),
44                        "`{}` must return `{}`",
45                        fn_name,
46                        VALIDATE_CALLBACK_RESULT
47                    );
48                } else if !is_infallible
49                    && !is_extern_result_callback_result(ty, VALIDATE_CALLBACK_RESULT)
50                {
51                    abort!(
52                        ty.span(),
53                        "`{}` must return `{}<{}>`",
54                        fn_name,
55                        EXTERN_RESULT,
56                        VALIDATE_CALLBACK_RESULT
57                    );
58                }
59            }
60            ("init", Some(return_type)) => {
61                if is_infallible && return_type != INIT_CALLBACK_RESULT {
62                    abort!(
63                        ty.span(),
64                        "`{}` must return `{}`",
65                        fn_name,
66                        INIT_CALLBACK_RESULT
67                    );
68                } else if !is_infallible
69                    && !is_extern_result_callback_result(ty, INIT_CALLBACK_RESULT)
70                {
71                    abort!(
72                        ty.span(),
73                        "`{}` must return `{}<{}>`",
74                        fn_name,
75                        EXTERN_RESULT,
76                        INIT_CALLBACK_RESULT
77                    );
78                }
79            }
80            ("post_commit", r) => {
81                let type_str = quote::quote!(#ty).to_string();
82
83                if r.is_some() && is_infallible {
84                    abort!(
85                        ty.span(),
86                        "`{}` must not have a return type", fn_name;
87                        help = "remove the `{}` return type", type_str
88                    );
89                } else if !is_extern_result_callback_result(ty, "()") {
90                    abort!(
91                        ty.span(),
92                        "`{}` must return `{}<{}>`",
93                        fn_name,
94                        EXTERN_RESULT,
95                        "()"
96                    );
97                }
98            }
99            (_, Some(return_type)) => {
100                let type_str = quote::quote!(#ty).to_string();
101
102                if is_infallible && return_type == EXTERN_RESULT {
103                    abort!(
104                        ty.span(),
105                        "functions marked as infallible must return the inner type directly"
106                    );
107                } else if !is_infallible && return_type != EXTERN_RESULT {
108                    abort!(
109                        ty.span(),
110                        "functions marked with #[hdk_extern] must return `{}` instead of `{}`", EXTERN_RESULT, type_str;
111                        help = "change the return type to `{}<{}>` or mark the function as infallible if it cannot fail #[hdk_extern(infallible)]", EXTERN_RESULT, type_str
112                    );
113                }
114            }
115            _ => {}
116        }
117    }
118
119    // extract the ident of the fn
120    // this will be exposed as the external facing extern
121    let external_fn_ident = item_fn.sig.ident.clone();
122    if item_fn.sig.inputs.len() > 1 {
123        abort_call_site!("hdk_extern functions must take a single parameter or none");
124    }
125    let input_type = if let Some(syn::FnArg::Typed(pat_type)) = item_fn.sig.inputs.first() {
126        pat_type.ty.clone()
127    } else {
128        let param_type = syn::Type::Verbatim(quote::quote! { () });
129        let param_pat = syn::Pat::Wild(syn::PatWild {
130            underscore_token: syn::token::Underscore::default(),
131            attrs: Vec::new(),
132        });
133        let param = syn::FnArg::Typed(syn::PatType {
134            attrs: Vec::new(),
135            pat: Box::new(param_pat),
136            colon_token: syn::token::Colon::default(),
137            ty: Box::new(param_type.clone()),
138        });
139        item_fn.sig.inputs.push(param);
140        Box::new(param_type)
141    };
142    let output_type = if let syn::ReturnType::Type(_, ref ty) = item_fn.sig.output {
143        ty.clone()
144    } else {
145        Box::new(syn::Type::Verbatim(quote::quote! { () }))
146    };
147
148    let internal_fn_ident = external_fn_ident.clone();
149
150    if is_infallible {
151        (quote::quote! {
152            map_extern_infallible!(#external_fn_ident, #internal_fn_ident, #input_type, #output_type);
153            #item_fn
154        })
155        .into()
156    } else {
157        (quote::quote! {
158            map_extern!(#external_fn_ident, #internal_fn_ident, #input_type, #output_type);
159            #item_fn
160        })
161        .into()
162    }
163}
164
165#[proc_macro_error]
166#[proc_macro_derive(EntryDefRegistration, attributes(entry_type))]
167pub fn derive_entry_type_registration(input: TokenStream) -> TokenStream {
168    entry_type_registration::derive(input)
169}
170
171#[proc_macro_error]
172#[proc_macro_derive(UnitEnum, attributes(unit_enum, unit_attrs))]
173pub fn derive_to_unit_enum(input: TokenStream) -> TokenStream {
174    unit_enum::derive(input)
175}
176
177/// Declares the integrity zome's entry types.
178///
179/// # Attributes
180/// - `unit_enum(TypeName)`: Defines the unit version of this enum. The resulting enum contains all
181/// entry types defined in the integrity zome. It can be used to refer to a type when needed.
182/// - `entry_def(name: String, required_validations: u8, visibility: String)`: Defines an entry type.
183///   - name: The name of the entry definition (optional).
184///     Defaults to the name of the enum variant.
185///   - required_validations: The number of validations required before this entry
186///     will not be published anymore (optional). Defaults to 5.
187///   - visibility: The visibility of this entry. [`public` | `private`].
188///     Default is `public`.
189///
190/// # Examples
191/// ```ignore
192/// #[hdk_entry_types]
193/// #[unit_enum(UnitEntryTypes)]
194/// pub enum EntryTypes {
195///     Post(Post),
196///     #[entry_type(required_validations = 5)]
197///     Msg(Msg),
198///     #[entry_type(name = "hidden_msg", required_validations = 5, visibility = "private")]
199///     PrivMsg(PrivMsg),
200/// }
201/// ```
202#[proc_macro_error]
203#[proc_macro_attribute]
204pub fn hdk_entry_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
205    entry_types::build(attrs, code)
206}
207
208/// Implements all the required types needed for a `LinkTypes` enum.
209#[proc_macro_error]
210#[proc_macro_attribute]
211pub fn hdk_link_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
212    link_types::build(attrs, code)
213}
214
215#[proc_macro_error]
216#[proc_macro_attribute]
217pub fn hdk_to_coordinates(attrs: TokenStream, code: TokenStream) -> TokenStream {
218    to_coordinates::build(attrs, code)
219}
220
221#[proc_macro_error]
222#[proc_macro_attribute]
223pub fn hdk_entry_types_name_registration(attrs: TokenStream, code: TokenStream) -> TokenStream {
224    entry_types_name_registration::build(attrs, code)
225}
226
227#[proc_macro_error]
228#[proc_macro_attribute]
229pub fn hdk_entry_types_conversions(attrs: TokenStream, code: TokenStream) -> TokenStream {
230    entry_types_conversions::build(attrs, code)
231}
232
233#[proc_macro_error]
234#[proc_macro_attribute]
235pub fn hdk_dependent_entry_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
236    entry_zomes::build(attrs, code)
237}
238
239#[proc_macro_error]
240#[proc_macro_attribute]
241pub fn hdk_dependent_link_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
242    link_zomes::build(attrs, code)
243}
244
245/// Helper for entry data types.
246///
247/// # Implements
248/// - `#[derive(Serialize, Deserialize, SerializedBytes, Debug)]`
249/// - `hdi::app_entry!`
250///
251/// # Examples
252/// ```ignore
253/// #[hdk_entry_helper]
254/// pub struct Post(pub String);
255/// ```
256#[proc_macro_error]
257#[proc_macro_attribute]
258pub fn hdk_entry_helper(attrs: TokenStream, code: TokenStream) -> TokenStream {
259    entry_helper::build(attrs, code)
260}
261
262/// Helper for decoding DNA Properties into a struct.
263///
264/// # Implements
265/// - [`holochain_integrity_types::TryFromDnaProperties`]
266///
267/// # Examples
268/// ```ignore
269/// #[dna_properties]
270/// pub struct MyDnaProperties {
271///     pub progenitor: String,
272///     pub max_length: u16,
273/// }
274///
275/// let my_props = MyDnaProperties::try_from_dna_properties()?;
276/// println!("The progenitor is {}", my_props.progenitor);
277/// ```
278#[proc_macro_error]
279#[proc_macro_attribute]
280pub fn dna_properties(attrs: TokenStream, code: TokenStream) -> TokenStream {
281    dna_properties::build(attrs, code)
282}