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