Skip to main content

picodata_plugin_proc_macro/
lib.rs

1mod internal;
2
3extern crate proc_macro;
4use proc_macro::TokenStream;
5
6use quote::quote;
7use syn::{parse_macro_input, Item, ReturnType, Signature};
8
9fn create_plugin_proc_macro_fn(
10    input: TokenStream,
11    export_name: &str,
12    parameter_type: &str,
13) -> TokenStream {
14    let parameter_type: syn::Type =
15        syn::parse_str(parameter_type).expect("failed to parse ServiceRegistry type");
16    let input = parse_macro_input!(input as syn::Item);
17
18    let syn::ItemFn { sig, block, .. } = match input {
19        syn::Item::Fn(f) => f,
20        _ => panic!("only `fn` items allowed"),
21    };
22
23    let (ident, inputs) = match sig {
24        Signature {
25            asyncness: Some(_), ..
26        } => {
27            panic!("async factories are not yet supported")
28        }
29        Signature {
30            variadic: Some(_), ..
31        } => {
32            panic!("variadic factories are not yet supported")
33        }
34        Signature {
35            ident,
36            output,
37            inputs,
38            generics,
39            ..
40        } => {
41            if !matches!(output, ReturnType::Default) {
42                panic!("function must have no output")
43            }
44
45            if !generics.params.is_empty() {
46                panic!("generic factories are not yet supported")
47            }
48
49            if inputs.len() != 1 {
50                panic!("only one input parameter is supported")
51            }
52
53            (ident, inputs)
54        }
55    };
56
57    let inner_fn_name =
58        syn::Ident::new(&("__inner_".to_string() + &ident.to_string()), ident.span());
59
60    quote! {
61        #[export_name = #export_name]
62        pub extern "C" fn #ident(registry: #parameter_type) {
63            #[inline(always)]
64            fn #inner_fn_name (#inputs) {
65                picodata_plugin::internal::set_panic_hook();
66
67                #block
68            }
69
70            #inner_fn_name(registry)
71        }
72    }
73    .into()
74}
75
76#[proc_macro_attribute]
77pub fn proc_service_registrar(_attr: TokenStream, input: TokenStream) -> TokenStream {
78    create_plugin_proc_macro_fn(
79        input,
80        "pico_service_registrar",
81        "&mut picodata_plugin::plugin::interface::ServiceRegistry",
82    )
83}
84
85/// A procedural macro that lets you set up the migration validator. Right now, you
86/// can validate only the migration context, but using the `set_context_validator` for
87/// the whole context validation, and `set_context_parameter_validator` for per-parameter
88/// validation. For more information, check out these functions' doc comments.
89///
90/// Example usage:
91/// ```ignore
92/// #[migration_validator]
93/// pub fn migration_validator(mv: &mut picodata_plugin::plugin::interface::MigrationValidator) {
94///     mv.set_context_validator(|ctx| {
95///         if ctx.len() >= 3 {
96///             Err("this context is too long, man".into())
97///         } else {
98///             Ok(())
99///         }
100///     });
101///     mv.set_context_parameter_validator(|k, v| match k.as_str() {
102///         "always_ok_parameter" => Ok(()),
103///         "always_bad_parameter" => Err("don't use this parameter, please".into()),
104///         "short_parameter" => {
105///             if v.len() > 15 {
106///                 Err("this parameter can't be that long!".into())
107///             } else {
108///                 Ok(())
109///             }
110///         }
111///         _ => Ok(()),
112///     });
113/// }
114/// ```
115#[proc_macro_attribute]
116pub fn proc_migration_validator(_attr: TokenStream, input: TokenStream) -> TokenStream {
117    create_plugin_proc_macro_fn(
118        input,
119        "pico_migration_validator",
120        "&mut picodata_plugin::plugin::interface::MigrationValidator",
121    )
122}
123
124/// Mark an API as picodata-internal.
125///
126/// You can apply this attribute to an item in your public API that you would
127/// like to expose to picodata, but do not want it exposed to plugin SDK users. This is
128/// useful when you want to share some piece of code between the plugin SDK and picodata,
129/// but do not want to commit any stability guarantees for.
130///
131/// This attribute does the following things to annotated items:
132///
133/// - Changes the visibility of the item from `pub` to `pub(crate)`, unless the
134///   `internal` crate feature is enabled. This ensures that internal code within
135///   the crate can always use the item, but downstream consumers cannot access
136///   it unless they opt-in to the `interal` API¸which only picodata should do.
137///   - Visibility of certain child items of the annotated item will also be
138///     changed to match the new item visibility, such as struct fields. Children
139///     that are not public will not be affected.
140///   - Child items of annotated modules will *not* have their visibility changed,
141///     as it might be desirable to be able to re-export them even if the module
142///     visibility is restricted. You should apply the attribute to each item
143///     within the module with the same feature name if you want to restrict the
144///     module's contents itself and not just the module namespace.
145/// - Appends an "Availability" section to the item's documentation that notes
146///   that the item is picodata-internal, and indicates the name of the crate feature to
147///   enable it.
148///
149/// Applying this attribute to non-`pub` items is pointless and does nothing.
150///
151/// # Examples
152///
153/// We can apply the attribute to a public function like so:
154///
155/// ```
156/// /// This function does something really risky!
157/// ///
158/// /// Don't use it yet!
159/// #[picodata_plugin_proc_macro::internal]
160/// pub fn risky_function() {
161///     unimplemented!()
162/// }
163/// ```
164///
165/// This will essentially be expanded to the following:
166///
167/// ```
168/// /// This function does something really risky!
169/// ///
170/// /// Don't use it yet!
171/// ///
172/// /// # Availability
173/// ///
174/// /// **This API is marked as picodata-internal** and is only available when the `internal` crate feature is enabled.
175/// /// This comes with no stability guarantees, and could be changed or removed at any time.
176/// #[cfg(feature = "internal")]
177/// pub fn risky_function() {
178///     unimplemented!()
179/// }
180///
181/// /// This function does something really risky!
182/// ///
183/// /// Don't use it yet!
184/// #[cfg(not(feature = "internal"))]
185/// pub(crate) fn risky_function() {
186///     unimplemented!()
187/// }
188/// ```
189#[proc_macro_attribute]
190pub fn internal(_args: TokenStream, input: TokenStream) -> TokenStream {
191    let attribute = internal::InternalAttribute;
192    match parse_macro_input!(input as Item) {
193        Item::Type(item_type) => attribute.expand(item_type),
194        Item::Enum(item_enum) => attribute.expand(item_enum),
195        Item::Struct(item_struct) => attribute.expand(item_struct),
196        Item::Fn(item_fn) => attribute.expand(item_fn),
197        Item::Mod(item_mod) => attribute.expand(item_mod),
198        Item::Trait(item_trait) => attribute.expand(item_trait),
199        Item::Const(item_const) => attribute.expand(item_const),
200        Item::Static(item_static) => attribute.expand(item_static),
201        Item::Use(item_use) => attribute.expand(item_use),
202        _ => panic!("unsupported item type"),
203    }
204}