spacetimedb_bindings_macro/
lib.rs1mod http;
12mod procedure;
13
14#[proc_macro_attribute]
15pub fn procedure(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
16 cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
17 let args = procedure::ProcedureArgs::parse(args)?;
18 procedure::procedure_impl(args, original_function)
19 })
20}
21
22#[proc_macro_attribute]
23pub fn http_handler(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
24 ok_or_compile_error(|| {
25 let item_ts: TokenStream = item.into();
26 let original_function: ItemFn = syn::parse2(item_ts)?;
27 http::handler_impl(args.into(), &original_function)
28 })
29}
30
31#[proc_macro_attribute]
32pub fn http_router(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
33 ok_or_compile_error(|| {
34 let item_ts: TokenStream = item.into();
35 let original_function: ItemFn = syn::parse2(item_ts)?;
36 http::router_impl(args.into(), &original_function)
37 })
38}
39mod reducer;
40
41#[proc_macro_attribute]
42pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
43 cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
44 let args = reducer::ReducerArgs::parse(args)?;
45 reducer::reducer_impl(args, original_function)
46 })
47}
48mod sats;
49mod table;
50
51#[proc_macro_attribute]
52pub fn table(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
53 let derive_table_helper: syn::Attribute = derive_table_helper_attr();
55
56 ok_or_compile_error(|| {
57 let item = TokenStream::from(item);
58 let mut derive_input: syn::DeriveInput = syn::parse2(item.clone())?;
59
60 if !derive_input.attrs.contains(&derive_table_helper) {
78 derive_input.attrs.push(derive_table_helper);
79 }
80
81 let args = table::TableArgs::parse(args.into(), &derive_input.ident)?;
82 let generated = table::table_impl(args, &derive_input)?;
83 Ok(TokenStream::from_iter([quote!(#derive_input), generated]))
84 })
85}
86mod util;
87mod view;
88
89#[proc_macro_attribute]
90pub fn view(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
91 let item_ts: TokenStream = item.into();
92 let original_function = match syn::parse2::<ItemFn>(item_ts.clone()) {
93 Ok(f) => f,
94 Err(e) => return TokenStream::from_iter([item_ts, e.into_compile_error()]).into(),
95 };
96 let args = match view::ViewArgs::parse(args.into(), &original_function.sig.ident) {
97 Ok(a) => a,
98 Err(e) => return TokenStream::from_iter([item_ts, e.into_compile_error()]).into(),
99 };
100 match view::view_impl(args, &original_function) {
101 Ok(ts) => ts.into(),
102 Err(e) => TokenStream::from_iter([item_ts, e.into_compile_error()]).into(),
103 }
104}
105
106use proc_macro::TokenStream as StdTokenStream;
107use proc_macro2::TokenStream;
108use quote::quote;
109use std::time::Duration;
110use syn::{parse::ParseStream, Attribute};
111use syn::{ItemConst, ItemFn};
112use util::{cvt_attr, ok_or_compile_error};
113
114mod sym {
115 pub struct Symbol(&'static str);
118
119 macro_rules! symbol {
120 ($ident:ident) => {
121 symbol!($ident, $ident);
122 };
123 ($const:ident, $ident:ident) => {
124 #[allow(non_upper_case_globals)]
125 #[doc = concat!("Matches `", stringify!($ident), "`.")]
126 pub const $const: Symbol = Symbol(stringify!($ident));
127 };
128 }
129
130 symbol!(accessor);
131 symbol!(at);
132 symbol!(auto_inc);
133 symbol!(btree);
134 symbol!(client_connected);
135 symbol!(client_disconnected);
136 symbol!(column);
137 symbol!(columns);
138 symbol!(crate_, crate);
139 symbol!(direct);
140 symbol!(hash);
141 symbol!(index);
142 symbol!(init);
143 symbol!(name);
144 symbol!(primary_key);
145 symbol!(private);
146 symbol!(public);
147 symbol!(repr);
148 symbol!(sats);
149 symbol!(scheduled);
150 symbol!(unique);
151 symbol!(update);
152 symbol!(default);
153 symbol!(event);
154
155 symbol!(u8);
156 symbol!(i8);
157 symbol!(u16);
158 symbol!(i16);
159 symbol!(u32);
160 symbol!(i32);
161 symbol!(u64);
162 symbol!(i64);
163 symbol!(u128);
164 symbol!(i128);
165 symbol!(f32);
166 symbol!(f64);
167
168 impl PartialEq<Symbol> for syn::Ident {
169 fn eq(&self, sym: &Symbol) -> bool {
170 self == sym.0
171 }
172 }
173 impl PartialEq<Symbol> for &syn::Ident {
174 fn eq(&self, sym: &Symbol) -> bool {
175 *self == sym.0
176 }
177 }
178 impl PartialEq<Symbol> for syn::Path {
179 fn eq(&self, sym: &Symbol) -> bool {
180 self.is_ident(sym)
181 }
182 }
183 impl PartialEq<Symbol> for &syn::Path {
184 fn eq(&self, sym: &Symbol) -> bool {
185 self.is_ident(sym)
186 }
187 }
188 impl std::fmt::Display for Symbol {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 f.write_str(self.0)
191 }
192 }
193 impl std::borrow::Borrow<str> for Symbol {
194 fn borrow(&self) -> &str {
195 self.0
196 }
197 }
198}
199
200fn derive_table_helper_attr() -> Attribute {
207 let source = quote!(#[derive(spacetimedb::__TableHelper)]);
208
209 syn::parse::Parser::parse2(Attribute::parse_outer, source)
210 .unwrap()
211 .into_iter()
212 .next()
213 .unwrap()
214}
215
216#[doc(hidden)]
220#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, default))]
221pub fn table_helper(input: StdTokenStream) -> StdTokenStream {
222 schema_type(input)
223}
224
225#[proc_macro]
226pub fn duration(input: StdTokenStream) -> StdTokenStream {
227 let dur = syn::parse_macro_input!(input with parse_duration);
228 let (secs, nanos) = (dur.as_secs(), dur.subsec_nanos());
229 quote!({
230 const DUR: ::core::time::Duration = ::core::time::Duration::new(#secs, #nanos);
231 DUR
232 })
233 .into()
234}
235
236fn parse_duration(input: ParseStream) -> syn::Result<Duration> {
237 let lookahead = input.lookahead1();
238 let (s, span) = if lookahead.peek(syn::LitStr) {
239 let s = input.parse::<syn::LitStr>()?;
240 (s.value(), s.span())
241 } else if lookahead.peek(syn::LitInt) {
242 let i = input.parse::<syn::LitInt>()?;
243 (i.to_string(), i.span())
244 } else {
245 return Err(lookahead.error());
246 };
247 humantime::parse_duration(&s).map_err(|e| syn::Error::new(span, format_args!("can't parse as duration: {e}")))
248}
249
250fn sats_derive(
252 input: StdTokenStream,
253 assume_in_module: bool,
254 logic: impl FnOnce(&sats::SatsType) -> TokenStream,
255) -> StdTokenStream {
256 let input = syn::parse_macro_input!(input as syn::DeriveInput);
257 let crate_fallback = if assume_in_module {
258 quote!(spacetimedb::spacetimedb_lib)
259 } else {
260 quote!(spacetimedb_lib)
261 };
262 sats::sats_type_from_derive(&input, crate_fallback)
263 .map(|ty| logic(&ty))
264 .unwrap_or_else(syn::Error::into_compile_error)
265 .into()
266}
267
268#[proc_macro_derive(Deserialize, attributes(sats))]
269pub fn deserialize(input: StdTokenStream) -> StdTokenStream {
270 sats_derive(input, false, sats::derive_deserialize)
271}
272
273#[proc_macro_derive(Serialize, attributes(sats))]
274pub fn serialize(input: StdTokenStream) -> StdTokenStream {
275 sats_derive(input, false, sats::derive_serialize)
276}
277
278#[proc_macro_derive(SpacetimeType, attributes(sats))]
279pub fn schema_type(input: StdTokenStream) -> StdTokenStream {
280 sats_derive(input, true, |ty| {
281 let ident = ty.ident;
282 let name = &ty.name;
283
284 let krate = &ty.krate;
285 TokenStream::from_iter([
286 sats::derive_satstype(ty),
287 sats::derive_deserialize(ty),
288 sats::derive_serialize(ty),
289 quote!(#krate::__make_register_reftype!(#ident, #name);),
291 ])
292 })
293}
294
295#[proc_macro_attribute]
296pub fn client_visibility_filter(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
297 ok_or_compile_error(|| {
298 if !args.is_empty() {
299 return Err(syn::Error::new_spanned(
300 TokenStream::from(args),
301 "The `client_visibility_filter` attribute does not accept arguments",
302 ));
303 }
304
305 let item: ItemConst = syn::parse(item)?;
306 let rls_ident = item.ident.clone();
307 let register_rls_symbol = format!("__preinit__20_register_row_level_security_{rls_ident}");
308
309 Ok(quote! {
310 #item
311
312 const _: () = {
313 #[unsafe(export_name = #register_rls_symbol)]
314 extern "C" fn __register_client_visibility_filter() {
315 spacetimedb::rt::register_row_level_security(#rls_ident.sql_text())
316 }
317 };
318 })
319 })
320}
321
322const KNOWN_SETTINGS: &[&str] = &["CASE_CONVERSION_POLICY"];
324
325#[proc_macro_attribute]
326pub fn settings(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
327 ok_or_compile_error(|| {
328 if !args.is_empty() {
329 return Err(syn::Error::new_spanned(
330 TokenStream::from(args),
331 "The `settings` attribute does not accept arguments",
332 ));
333 }
334
335 let item: ItemConst = syn::parse(item)?;
336 let ident = &item.ident;
337 let ident_str = ident.to_string();
338
339 if !KNOWN_SETTINGS.contains(&ident_str.as_str()) {
340 return Err(syn::Error::new_spanned(
341 ident,
342 format!(
343 "unknown setting `{ident_str}`. Known settings: {}",
344 KNOWN_SETTINGS.join(", ")
345 ),
346 ));
347 }
348
349 let register_symbol = format!("__preinit__05_setting_{ident_str}");
352
353 let register_call = match ident_str.as_str() {
355 "CASE_CONVERSION_POLICY" => quote! {
356 spacetimedb::rt::register_case_conversion_policy(#ident)
357 },
358 _ => unreachable!("validated above"),
359 };
360
361 Ok(quote! {
362 #item
363
364 const _: () = {
365 #[unsafe(export_name = #register_symbol)]
366 extern "C" fn __register_setting() {
367 #register_call
368 }
369 };
370 })
371 })
372}