hdk_derive/
lib.rs

1#![crate_type = "proc-macro"]
2#![allow(clippy::manual_unwrap_or_default)] // Fixing requires a `darling` upgrade
3
4use proc_macro::TokenStream;
5use proc_macro_error::abort;
6use proc_macro_error::abort_call_site;
7use proc_macro_error::proc_macro_error;
8use quote::TokenStreamExt;
9use syn::parse::Parse;
10use syn::parse::ParseStream;
11use syn::parse::Result;
12use syn::punctuated::Punctuated;
13use syn::spanned::Spanned;
14use util::get_return_type_ident;
15use util::is_extern_result_callback_result;
16
17mod dna_properties;
18mod entry_helper;
19mod entry_type_registration;
20mod entry_types;
21mod entry_types_conversions;
22mod entry_types_name_registration;
23mod entry_zomes;
24mod link_types;
25mod link_zomes;
26mod to_coordinates;
27mod unit_enum;
28mod util;
29
30struct EntryDef(holochain_integrity_types::entry_def::EntryDef);
31struct EntryDefId(holochain_integrity_types::entry_def::EntryDefId);
32struct EntryVisibility(holochain_integrity_types::entry_def::EntryVisibility);
33struct RequiredValidations(holochain_integrity_types::entry_def::RequiredValidations);
34
35impl Parse for EntryDef {
36    fn parse(input: ParseStream) -> Result<Self> {
37        let mut id =
38            holochain_integrity_types::entry_def::EntryDefId::App(String::default().into());
39        let mut required_validations =
40            holochain_integrity_types::entry_def::RequiredValidations::default();
41        let mut visibility = holochain_integrity_types::entry_def::EntryVisibility::default();
42
43        let vars = Punctuated::<syn::MetaNameValue, syn::Token![,]>::parse_terminated(input)?;
44        for var in vars {
45            if let Some(segment) = var.path.segments.first() {
46                match segment.ident.to_string().as_str() {
47                    "id" => match var.lit {
48                        syn::Lit::Str(s) => {
49                            id = holochain_integrity_types::entry_def::EntryDefId::App(
50                                s.value().to_string().into(),
51                            )
52                        }
53                        _ => unreachable!(),
54                    },
55                    "required_validations" => match var.lit {
56                        syn::Lit::Int(i) => {
57                            required_validations =
58                                holochain_integrity_types::entry_def::RequiredValidations::from(
59                                    i.base10_parse::<u8>()?,
60                                )
61                        }
62                        _ => unreachable!(),
63                    },
64                    "visibility" => {
65                        match var.lit {
66                            syn::Lit::Str(s) => visibility = match s.value().as_str() {
67                                "public" => {
68                                    holochain_integrity_types::entry_def::EntryVisibility::Public
69                                }
70                                "private" => {
71                                    holochain_integrity_types::entry_def::EntryVisibility::Private
72                                }
73                                _ => unreachable!(),
74                            },
75                            _ => unreachable!(),
76                        };
77                    }
78                    _ => {}
79                }
80            }
81        }
82        Ok(EntryDef(holochain_integrity_types::entry_def::EntryDef {
83            id,
84            visibility,
85            required_validations,
86            cache_at_agent_activity: false,
87        }))
88    }
89}
90
91impl quote::ToTokens for EntryDefId {
92    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
93        match &self.0 {
94            holochain_integrity_types::entry_def::EntryDefId::App(s) => {
95                let string: String = s.0.to_string();
96                tokens.append_all(quote::quote! {
97                    hdi::prelude::EntryDefId::App(#string.into())
98                });
99            }
100            _ => unreachable!(),
101        }
102    }
103}
104
105impl quote::ToTokens for RequiredValidations {
106    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
107        let u = <u8>::from(self.0);
108        tokens.append_all(quote::quote! {
109            hdi::prelude::RequiredValidations::from(#u)
110        });
111    }
112}
113
114impl quote::ToTokens for EntryVisibility {
115    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
116        let variant = syn::Ident::new(
117            match self.0 {
118                holochain_integrity_types::entry_def::EntryVisibility::Public => "Public",
119                holochain_integrity_types::entry_def::EntryVisibility::Private => "Private",
120            },
121            proc_macro2::Span::call_site(),
122        );
123        tokens.append_all(quote::quote! {
124            hdi::prelude::EntryVisibility::#variant
125        });
126    }
127}
128
129impl quote::ToTokens for EntryDef {
130    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
131        let id = EntryDefId(self.0.id.clone());
132        let visibility = EntryVisibility(self.0.visibility);
133        let required_validations = RequiredValidations(self.0.required_validations);
134
135        tokens.append_all(quote::quote! {
136            hdi::prelude::EntryDef {
137                id: #id,
138                visibility: #visibility,
139                required_validations: #required_validations,
140            }
141        });
142    }
143}
144
145#[proc_macro_error]
146#[proc_macro_attribute]
147pub fn hdk_extern(attrs: TokenStream, item: TokenStream) -> TokenStream {
148    // extern mapping is only valid for functions
149    let mut item_fn = syn::parse_macro_input!(item as syn::ItemFn);
150
151    let fn_name = item_fn.sig.ident.to_string();
152    let is_infallible = attrs.to_string() == "infallible";
153
154    // Check return type
155    if let syn::ReturnType::Type(_, ref ty) = item_fn.sig.output {
156        const EXTERN_RESULT: &str = "ExternResult";
157        const VALIDATE_CALLBACK_RESULT: &str = "ValidateCallbackResult";
158        const INIT_CALLBACK_RESULT: &str = "InitCallbackResult";
159
160        match (fn_name.as_str(), get_return_type_ident(ty)) {
161            ("validate" | "genesis_self_check", Some(return_type)) => {
162                if is_infallible && return_type != VALIDATE_CALLBACK_RESULT {
163                    abort!(
164                        ty.span(),
165                        "`{}` must return `{}`",
166                        fn_name,
167                        VALIDATE_CALLBACK_RESULT
168                    );
169                } else if !is_infallible
170                    && !is_extern_result_callback_result(ty, VALIDATE_CALLBACK_RESULT)
171                {
172                    abort!(
173                        ty.span(),
174                        "`{}` must return `{}<{}>`",
175                        fn_name,
176                        EXTERN_RESULT,
177                        VALIDATE_CALLBACK_RESULT
178                    );
179                }
180            }
181            ("init", Some(return_type)) => {
182                if is_infallible && return_type != INIT_CALLBACK_RESULT {
183                    abort!(
184                        ty.span(),
185                        "`{}` must return `{}`",
186                        fn_name,
187                        INIT_CALLBACK_RESULT
188                    );
189                } else if !is_infallible
190                    && !is_extern_result_callback_result(ty, INIT_CALLBACK_RESULT)
191                {
192                    abort!(
193                        ty.span(),
194                        "`{}` must return `{}<{}>`",
195                        fn_name,
196                        EXTERN_RESULT,
197                        INIT_CALLBACK_RESULT
198                    );
199                }
200            }
201            ("post_commit", r) => {
202                let type_str = quote::quote!(#ty).to_string();
203
204                if r.is_some() && is_infallible {
205                    abort!(
206                        ty.span(),
207                        "`{}` must not have a return type", fn_name;
208                        help = "remove the `{}` return type", type_str
209                    );
210                } else if !is_extern_result_callback_result(ty, "()") {
211                    abort!(
212                        ty.span(),
213                        "`{}` must return `{}<{}>`",
214                        fn_name,
215                        EXTERN_RESULT,
216                        "()"
217                    );
218                }
219            }
220            (_, Some(return_type)) => {
221                let type_str = quote::quote!(#ty).to_string();
222
223                if is_infallible && return_type == EXTERN_RESULT {
224                    abort!(
225                        ty.span(),
226                        "functions marked as infallible must return the inner type directly"
227                    );
228                } else if !is_infallible && return_type != EXTERN_RESULT {
229                    abort!(
230                        ty.span(),
231                        "functions marked with #[hdk_extern] must return `{}` instead of `{}`", EXTERN_RESULT, type_str;
232                        help = "change the return type to `{}<{}>` or mark the function as infallible if it cannot fail #[hdk_extern(infallible)]", EXTERN_RESULT, type_str
233                    );
234                }
235            }
236            _ => {}
237        }
238    }
239
240    // extract the ident of the fn
241    // this will be exposed as the external facing extern
242    let external_fn_ident = item_fn.sig.ident.clone();
243    if item_fn.sig.inputs.len() > 1 {
244        abort_call_site!("hdk_extern functions must take a single parameter or none");
245    }
246    let input_type = if let Some(syn::FnArg::Typed(pat_type)) = item_fn.sig.inputs.first() {
247        pat_type.ty.clone()
248    } else {
249        let param_type = syn::Type::Verbatim(quote::quote! { () });
250        let param_pat = syn::Pat::Wild(syn::PatWild {
251            underscore_token: syn::token::Underscore::default(),
252            attrs: Vec::new(),
253        });
254        let param = syn::FnArg::Typed(syn::PatType {
255            attrs: Vec::new(),
256            pat: Box::new(param_pat),
257            colon_token: syn::token::Colon::default(),
258            ty: Box::new(param_type.clone()),
259        });
260        item_fn.sig.inputs.push(param);
261        Box::new(param_type)
262    };
263    let output_type = if let syn::ReturnType::Type(_, ref ty) = item_fn.sig.output {
264        ty.clone()
265    } else {
266        Box::new(syn::Type::Verbatim(quote::quote! { () }))
267    };
268
269    let internal_fn_ident = external_fn_ident.clone();
270
271    if is_infallible {
272        (quote::quote! {
273            map_extern_infallible!(#external_fn_ident, #internal_fn_ident, #input_type, #output_type);
274            #item_fn
275        })
276        .into()
277    } else {
278        (quote::quote! {
279            map_extern!(#external_fn_ident, #internal_fn_ident, #input_type, #output_type);
280            #item_fn
281        })
282        .into()
283    }
284}
285
286#[proc_macro_error]
287#[proc_macro_derive(EntryDefRegistration, attributes(entry_type))]
288pub fn derive_entry_type_registration(input: TokenStream) -> TokenStream {
289    entry_type_registration::derive(input)
290}
291
292#[proc_macro_error]
293#[proc_macro_derive(UnitEnum, attributes(unit_enum, unit_attrs))]
294pub fn derive_to_unit_enum(input: TokenStream) -> TokenStream {
295    unit_enum::derive(input)
296}
297
298/// Declares the integrity zome's entry types.
299///
300/// # Attributes
301/// - `unit_enum(TypeName)`: Defines the unit version of this enum. The resulting enum contains all
302/// entry types defined in the integrity zome. It can be used to refer to a type when needed.
303/// - `entry_def(name: String, required_validations: u8, visibility: String)`: Defines an entry type.
304///   - name: The name of the entry definition (optional).
305///     Defaults to the name of the enum variant.
306///   - required_validations: The number of validations required before this entry
307///     will not be published anymore (optional). Defaults to 5.
308///   - visibility: The visibility of this entry. [`public` | `private`].
309///     Default is `public`.
310///
311/// # Examples
312/// ```ignore
313/// #[hdk_entry_types]
314/// #[unit_enum(UnitEntryTypes)]
315/// pub enum EntryTypes {
316///     Post(Post),
317///     #[entry_type(required_validations = 5)]
318///     Msg(Msg),
319///     #[entry_type(name = "hidden_msg", required_validations = 5, visibility = "private")]
320///     PrivMsg(PrivMsg),
321/// }
322/// ```
323#[proc_macro_error]
324#[proc_macro_attribute]
325pub fn hdk_entry_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
326    entry_types::build(attrs, code)
327}
328
329/// Implements all the required types needed for a `LinkTypes` enum.
330#[proc_macro_error]
331#[proc_macro_attribute]
332pub fn hdk_link_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
333    link_types::build(attrs, code)
334}
335
336#[proc_macro_error]
337#[proc_macro_attribute]
338pub fn hdk_to_coordinates(attrs: TokenStream, code: TokenStream) -> TokenStream {
339    to_coordinates::build(attrs, code)
340}
341
342#[proc_macro_error]
343#[proc_macro_attribute]
344pub fn hdk_entry_types_name_registration(attrs: TokenStream, code: TokenStream) -> TokenStream {
345    entry_types_name_registration::build(attrs, code)
346}
347
348#[proc_macro_error]
349#[proc_macro_attribute]
350pub fn hdk_entry_types_conversions(attrs: TokenStream, code: TokenStream) -> TokenStream {
351    entry_types_conversions::build(attrs, code)
352}
353
354#[proc_macro_error]
355#[proc_macro_attribute]
356pub fn hdk_dependent_entry_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
357    entry_zomes::build(attrs, code)
358}
359
360#[proc_macro_error]
361#[proc_macro_attribute]
362pub fn hdk_dependent_link_types(attrs: TokenStream, code: TokenStream) -> TokenStream {
363    link_zomes::build(attrs, code)
364}
365
366/// Helper for entry data types.
367///
368/// # Implements
369/// - `#[derive(Serialize, Deserialize, SerializedBytes, Debug)]`
370/// - `hdi::app_entry!`
371///
372/// # Examples
373/// ```ignore
374/// #[hdk_entry_helper]
375/// pub struct Post(pub String);
376/// ```
377#[proc_macro_error]
378#[proc_macro_attribute]
379pub fn hdk_entry_helper(attrs: TokenStream, code: TokenStream) -> TokenStream {
380    entry_helper::build(attrs, code)
381}
382
383/// Helper for decoding DNA Properties into a struct.
384///
385/// # Implements
386/// - [`holochain_integrity_types::TryFromDnaProperties`]
387///
388/// # Examples
389/// ```ignore
390/// #[dna_properties]
391/// pub struct MyDnaProperties {
392///     pub progenitor: String,
393///     pub max_length: u16,
394/// }
395///
396/// let my_props = MyDnaProperties::try_from_dna_properties()?;
397/// println!("The progenitor is {}", my_props.progenitor);
398/// ```
399#[proc_macro_error]
400#[proc_macro_attribute]
401pub fn dna_properties(attrs: TokenStream, code: TokenStream) -> TokenStream {
402    dna_properties::build(attrs, code)
403}