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}