Skip to main content

picodata_plugin_proc_macro/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3
4use quote::quote;
5use syn::{parse_macro_input, ReturnType, Signature};
6
7fn create_plugin_proc_macro_fn(
8    input: TokenStream,
9    export_name: &str,
10    parameter_type: &str,
11) -> TokenStream {
12    let parameter_type: syn::Type =
13        syn::parse_str(parameter_type).expect("failed to parse ServiceRegistry type");
14    let input = parse_macro_input!(input as syn::Item);
15
16    let syn::ItemFn { sig, block, .. } = match input {
17        syn::Item::Fn(f) => f,
18        _ => panic!("only `fn` items allowed"),
19    };
20
21    let (ident, inputs) = match sig {
22        Signature {
23            asyncness: Some(_), ..
24        } => {
25            panic!("async factories are not yet supported")
26        }
27        Signature {
28            variadic: Some(_), ..
29        } => {
30            panic!("variadic factories are not yet supported")
31        }
32        Signature {
33            ident,
34            output,
35            inputs,
36            generics,
37            ..
38        } => {
39            if !matches!(output, ReturnType::Default) {
40                panic!("function must have no output")
41            }
42
43            if !generics.params.is_empty() {
44                panic!("generic factories are not yet supported")
45            }
46
47            if inputs.len() != 1 {
48                panic!("only one input parameter is supported")
49            }
50
51            (ident, inputs)
52        }
53    };
54
55    let inner_fn_name =
56        syn::Ident::new(&("__inner_".to_string() + &ident.to_string()), ident.span());
57
58    quote! {
59        #[export_name = #export_name]
60        pub extern "C" fn #ident(registry: #parameter_type) {
61            #[inline(always)]
62            fn #inner_fn_name (#inputs) {
63                picodata_plugin::internal::set_panic_hook();
64
65                #block
66            }
67
68            #inner_fn_name(registry)
69        }
70    }
71    .into()
72}
73
74#[proc_macro_attribute]
75pub fn proc_service_registrar(_attr: TokenStream, input: TokenStream) -> TokenStream {
76    create_plugin_proc_macro_fn(
77        input,
78        "pico_service_registrar",
79        "&mut picodata_plugin::plugin::interface::ServiceRegistry",
80    )
81}
82
83/// A procedural macro that lets you set up the migration validator. Right now, you
84/// can validate only the migration context, but using the `set_context_validator` for
85/// the whole context validation, and `set_context_parameter_validator` for per-parameter
86/// validation. For more information, check out these functions' doc comments.
87///
88/// Example usage:
89/// ```ignore
90/// #[migration_validator]
91/// pub fn migration_validator(mv: &mut picodata_plugin::plugin::interface::MigrationValidator) {
92///     mv.set_context_validator(|ctx| {
93///         if ctx.len() >= 3 {
94///             Err("this context is too long, man".into())
95///         } else {
96///             Ok(())
97///         }
98///     });
99///     mv.set_context_parameter_validator(|k, v| match k.as_str() {
100///         "always_ok_parameter" => Ok(()),
101///         "always_bad_parameter" => Err("don't use this parameter, please".into()),
102///         "short_parameter" => {
103///             if v.len() > 15 {
104///                 Err("this parameter can't be that long!".into())
105///             } else {
106///                 Ok(())
107///             }
108///         }
109///         _ => Ok(()),
110///     });
111/// }
112/// ```
113#[proc_macro_attribute]
114pub fn proc_migration_validator(_attr: TokenStream, input: TokenStream) -> TokenStream {
115    create_plugin_proc_macro_fn(
116        input,
117        "pico_migration_validator",
118        "&mut picodata_plugin::plugin::interface::MigrationValidator",
119    )
120}