spacetimedb_bindings_macro/
lib.rs1mod procedure;
12mod reducer;
13mod sats;
14mod table;
15mod util;
16mod view;
17
18use proc_macro::TokenStream as StdTokenStream;
19use proc_macro2::TokenStream;
20use quote::quote;
21use std::time::Duration;
22use syn::{parse::ParseStream, Attribute};
23use syn::{ItemConst, ItemFn};
24use util::{cvt_attr, ok_or_compile_error};
25
26mod sym {
27 pub struct Symbol(&'static str);
30
31 macro_rules! symbol {
32 ($ident:ident) => {
33 symbol!($ident, $ident);
34 };
35 ($const:ident, $ident:ident) => {
36 #[allow(non_upper_case_globals)]
37 #[doc = concat!("Matches `", stringify!($ident), "`.")]
38 pub const $const: Symbol = Symbol(stringify!($ident));
39 };
40 }
41
42 symbol!(at);
43 symbol!(auto_inc);
44 symbol!(btree);
45 symbol!(client_connected);
46 symbol!(client_disconnected);
47 symbol!(column);
48 symbol!(columns);
49 symbol!(crate_, crate);
50 symbol!(direct);
51 symbol!(index);
52 symbol!(init);
53 symbol!(name);
54 symbol!(primary_key);
55 symbol!(private);
56 symbol!(public);
57 symbol!(repr);
58 symbol!(sats);
59 symbol!(scheduled);
60 symbol!(unique);
61 symbol!(update);
62 symbol!(default);
63
64 symbol!(u8);
65 symbol!(i8);
66 symbol!(u16);
67 symbol!(i16);
68 symbol!(u32);
69 symbol!(i32);
70 symbol!(u64);
71 symbol!(i64);
72 symbol!(u128);
73 symbol!(i128);
74 symbol!(f32);
75 symbol!(f64);
76
77 impl PartialEq<Symbol> for syn::Ident {
78 fn eq(&self, sym: &Symbol) -> bool {
79 self == sym.0
80 }
81 }
82 impl PartialEq<Symbol> for &syn::Ident {
83 fn eq(&self, sym: &Symbol) -> bool {
84 *self == sym.0
85 }
86 }
87 impl PartialEq<Symbol> for syn::Path {
88 fn eq(&self, sym: &Symbol) -> bool {
89 self.is_ident(sym)
90 }
91 }
92 impl PartialEq<Symbol> for &syn::Path {
93 fn eq(&self, sym: &Symbol) -> bool {
94 self.is_ident(sym)
95 }
96 }
97 impl std::fmt::Display for Symbol {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.write_str(self.0)
100 }
101 }
102 impl std::borrow::Borrow<str> for Symbol {
103 fn borrow(&self) -> &str {
104 self.0
105 }
106 }
107}
108
109#[proc_macro_attribute]
110pub fn procedure(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
111 cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
112 let args = procedure::ProcedureArgs::parse(args)?;
113 procedure::procedure_impl(args, original_function)
114 })
115}
116
117#[proc_macro_attribute]
118pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
119 cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
120 let args = reducer::ReducerArgs::parse(args)?;
121 reducer::reducer_impl(args, original_function)
122 })
123}
124
125#[proc_macro_attribute]
126pub fn view(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
127 cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
128 let args = view::ViewArgs::parse(args, &original_function.sig.ident)?;
129 view::view_impl(args, original_function)
130 })
131}
132
133fn derive_table_helper_attr() -> Attribute {
140 let source = quote!(#[derive(spacetimedb::__TableHelper)]);
141
142 syn::parse::Parser::parse2(Attribute::parse_outer, source)
143 .unwrap()
144 .into_iter()
145 .next()
146 .unwrap()
147}
148
149#[proc_macro_attribute]
150pub fn table(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
151 let derive_table_helper: syn::Attribute = derive_table_helper_attr();
153
154 ok_or_compile_error(|| {
155 let item = TokenStream::from(item);
156 let mut derive_input: syn::DeriveInput = syn::parse2(item.clone())?;
157
158 if !derive_input.attrs.contains(&derive_table_helper) {
176 derive_input.attrs.push(derive_table_helper);
177 }
178
179 let args = table::TableArgs::parse(args.into(), &derive_input.ident)?;
180 let generated = table::table_impl(args, &derive_input)?;
181 Ok(TokenStream::from_iter([quote!(#derive_input), generated]))
182 })
183}
184
185#[doc(hidden)]
189#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, default))]
190pub fn table_helper(input: StdTokenStream) -> StdTokenStream {
191 schema_type(input)
192}
193
194#[proc_macro]
195pub fn duration(input: StdTokenStream) -> StdTokenStream {
196 let dur = syn::parse_macro_input!(input with parse_duration);
197 let (secs, nanos) = (dur.as_secs(), dur.subsec_nanos());
198 quote!({
199 const DUR: ::core::time::Duration = ::core::time::Duration::new(#secs, #nanos);
200 DUR
201 })
202 .into()
203}
204
205fn parse_duration(input: ParseStream) -> syn::Result<Duration> {
206 let lookahead = input.lookahead1();
207 let (s, span) = if lookahead.peek(syn::LitStr) {
208 let s = input.parse::<syn::LitStr>()?;
209 (s.value(), s.span())
210 } else if lookahead.peek(syn::LitInt) {
211 let i = input.parse::<syn::LitInt>()?;
212 (i.to_string(), i.span())
213 } else {
214 return Err(lookahead.error());
215 };
216 humantime::parse_duration(&s).map_err(|e| syn::Error::new(span, format_args!("can't parse as duration: {e}")))
217}
218
219fn sats_derive(
221 input: StdTokenStream,
222 assume_in_module: bool,
223 logic: impl FnOnce(&sats::SatsType) -> TokenStream,
224) -> StdTokenStream {
225 let input = syn::parse_macro_input!(input as syn::DeriveInput);
226 let crate_fallback = if assume_in_module {
227 quote!(spacetimedb::spacetimedb_lib)
228 } else {
229 quote!(spacetimedb_lib)
230 };
231 sats::sats_type_from_derive(&input, crate_fallback)
232 .map(|ty| logic(&ty))
233 .unwrap_or_else(syn::Error::into_compile_error)
234 .into()
235}
236
237#[proc_macro_derive(Deserialize, attributes(sats))]
238pub fn deserialize(input: StdTokenStream) -> StdTokenStream {
239 sats_derive(input, false, sats::derive_deserialize)
240}
241
242#[proc_macro_derive(Serialize, attributes(sats))]
243pub fn serialize(input: StdTokenStream) -> StdTokenStream {
244 sats_derive(input, false, sats::derive_serialize)
245}
246
247#[proc_macro_derive(SpacetimeType, attributes(sats))]
248pub fn schema_type(input: StdTokenStream) -> StdTokenStream {
249 sats_derive(input, true, |ty| {
250 let ident = ty.ident;
251 let name = &ty.name;
252
253 let krate = &ty.krate;
254 TokenStream::from_iter([
255 sats::derive_satstype(ty),
256 sats::derive_deserialize(ty),
257 sats::derive_serialize(ty),
258 quote!(#krate::__make_register_reftype!(#ident, #name);),
260 ])
261 })
262}
263
264#[proc_macro_attribute]
265pub fn client_visibility_filter(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
266 ok_or_compile_error(|| {
267 if !args.is_empty() {
268 return Err(syn::Error::new_spanned(
269 TokenStream::from(args),
270 "The `client_visibility_filter` attribute does not accept arguments",
271 ));
272 }
273
274 let item: ItemConst = syn::parse(item)?;
275 let rls_ident = item.ident.clone();
276 let register_rls_symbol = format!("__preinit__20_register_row_level_security_{rls_ident}");
277
278 Ok(quote! {
279 #item
280
281 const _: () = {
282 #[export_name = #register_rls_symbol]
283 extern "C" fn __register_client_visibility_filter() {
284 spacetimedb::rt::register_row_level_security(#rls_ident.sql_text())
285 }
286 };
287 })
288 })
289}