mod http;
mod procedure;
#[proc_macro_attribute]
pub fn procedure(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
let args = procedure::ProcedureArgs::parse(args)?;
procedure::procedure_impl(args, original_function)
})
}
#[proc_macro_attribute]
pub fn http_handler(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
ok_or_compile_error(|| {
let item_ts: TokenStream = item.into();
let original_function: ItemFn = syn::parse2(item_ts)?;
http::handler_impl(args.into(), &original_function)
})
}
#[proc_macro_attribute]
pub fn http_router(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
ok_or_compile_error(|| {
let item_ts: TokenStream = item.into();
let original_function: ItemFn = syn::parse2(item_ts)?;
http::router_impl(args.into(), &original_function)
})
}
mod reducer;
#[proc_macro_attribute]
pub fn reducer(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
cvt_attr::<ItemFn>(args, item, quote!(), |args, original_function| {
let args = reducer::ReducerArgs::parse(args)?;
reducer::reducer_impl(args, original_function)
})
}
mod sats;
mod table;
#[proc_macro_attribute]
pub fn table(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
let derive_table_helper: syn::Attribute = derive_table_helper_attr();
ok_or_compile_error(|| {
let item = TokenStream::from(item);
let mut derive_input: syn::DeriveInput = syn::parse2(item.clone())?;
if !derive_input.attrs.contains(&derive_table_helper) {
derive_input.attrs.push(derive_table_helper);
}
let args = table::TableArgs::parse(args.into(), &derive_input.ident)?;
let generated = table::table_impl(args, &derive_input)?;
Ok(TokenStream::from_iter([quote!(#derive_input), generated]))
})
}
mod util;
mod view;
#[proc_macro_attribute]
pub fn view(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
let item_ts: TokenStream = item.into();
let original_function = match syn::parse2::<ItemFn>(item_ts.clone()) {
Ok(f) => f,
Err(e) => return TokenStream::from_iter([item_ts, e.into_compile_error()]).into(),
};
let args = match view::ViewArgs::parse(args.into(), &original_function.sig.ident) {
Ok(a) => a,
Err(e) => return TokenStream::from_iter([item_ts, e.into_compile_error()]).into(),
};
match view::view_impl(args, &original_function) {
Ok(ts) => ts.into(),
Err(e) => TokenStream::from_iter([item_ts, e.into_compile_error()]).into(),
}
}
use proc_macro::TokenStream as StdTokenStream;
use proc_macro2::TokenStream;
use quote::quote;
use std::time::Duration;
use syn::{parse::ParseStream, Attribute};
use syn::{ItemConst, ItemFn};
use util::{cvt_attr, ok_or_compile_error};
mod sym {
pub struct Symbol(&'static str);
macro_rules! symbol {
($ident:ident) => {
symbol!($ident, $ident);
};
($const:ident, $ident:ident) => {
#[allow(non_upper_case_globals)]
#[doc = concat!("Matches `", stringify!($ident), "`.")]
pub const $const: Symbol = Symbol(stringify!($ident));
};
}
symbol!(accessor);
symbol!(at);
symbol!(auto_inc);
symbol!(btree);
symbol!(client_connected);
symbol!(client_disconnected);
symbol!(column);
symbol!(columns);
symbol!(crate_, crate);
symbol!(direct);
symbol!(hash);
symbol!(index);
symbol!(init);
symbol!(name);
symbol!(primary_key);
symbol!(private);
symbol!(public);
symbol!(repr);
symbol!(sats);
symbol!(scheduled);
symbol!(unique);
symbol!(update);
symbol!(default);
symbol!(event);
symbol!(u8);
symbol!(i8);
symbol!(u16);
symbol!(i16);
symbol!(u32);
symbol!(i32);
symbol!(u64);
symbol!(i64);
symbol!(u128);
symbol!(i128);
symbol!(f32);
symbol!(f64);
impl PartialEq<Symbol> for syn::Ident {
fn eq(&self, sym: &Symbol) -> bool {
self == sym.0
}
}
impl PartialEq<Symbol> for &syn::Ident {
fn eq(&self, sym: &Symbol) -> bool {
*self == sym.0
}
}
impl PartialEq<Symbol> for syn::Path {
fn eq(&self, sym: &Symbol) -> bool {
self.is_ident(sym)
}
}
impl PartialEq<Symbol> for &syn::Path {
fn eq(&self, sym: &Symbol) -> bool {
self.is_ident(sym)
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
impl std::borrow::Borrow<str> for Symbol {
fn borrow(&self) -> &str {
self.0
}
}
}
fn derive_table_helper_attr() -> Attribute {
let source = quote!(#[derive(spacetimedb::__TableHelper)]);
syn::parse::Parser::parse2(Attribute::parse_outer, source)
.unwrap()
.into_iter()
.next()
.unwrap()
}
#[doc(hidden)]
#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, default))]
pub fn table_helper(input: StdTokenStream) -> StdTokenStream {
schema_type(input)
}
#[proc_macro]
pub fn duration(input: StdTokenStream) -> StdTokenStream {
let dur = syn::parse_macro_input!(input with parse_duration);
let (secs, nanos) = (dur.as_secs(), dur.subsec_nanos());
quote!({
const DUR: ::core::time::Duration = ::core::time::Duration::new(#secs, #nanos);
DUR
})
.into()
}
fn parse_duration(input: ParseStream) -> syn::Result<Duration> {
let lookahead = input.lookahead1();
let (s, span) = if lookahead.peek(syn::LitStr) {
let s = input.parse::<syn::LitStr>()?;
(s.value(), s.span())
} else if lookahead.peek(syn::LitInt) {
let i = input.parse::<syn::LitInt>()?;
(i.to_string(), i.span())
} else {
return Err(lookahead.error());
};
humantime::parse_duration(&s).map_err(|e| syn::Error::new(span, format_args!("can't parse as duration: {e}")))
}
fn sats_derive(
input: StdTokenStream,
assume_in_module: bool,
logic: impl FnOnce(&sats::SatsType) -> TokenStream,
) -> StdTokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let crate_fallback = if assume_in_module {
quote!(spacetimedb::spacetimedb_lib)
} else {
quote!(spacetimedb_lib)
};
sats::sats_type_from_derive(&input, crate_fallback)
.map(|ty| logic(&ty))
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro_derive(Deserialize, attributes(sats))]
pub fn deserialize(input: StdTokenStream) -> StdTokenStream {
sats_derive(input, false, sats::derive_deserialize)
}
#[proc_macro_derive(Serialize, attributes(sats))]
pub fn serialize(input: StdTokenStream) -> StdTokenStream {
sats_derive(input, false, sats::derive_serialize)
}
#[proc_macro_derive(SpacetimeType, attributes(sats))]
pub fn schema_type(input: StdTokenStream) -> StdTokenStream {
sats_derive(input, true, |ty| {
let ident = ty.ident;
let name = &ty.name;
let krate = &ty.krate;
TokenStream::from_iter([
sats::derive_satstype(ty),
sats::derive_deserialize(ty),
sats::derive_serialize(ty),
quote!(#krate::__make_register_reftype!(#ident, #name);),
])
})
}
#[proc_macro_attribute]
pub fn client_visibility_filter(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
ok_or_compile_error(|| {
if !args.is_empty() {
return Err(syn::Error::new_spanned(
TokenStream::from(args),
"The `client_visibility_filter` attribute does not accept arguments",
));
}
let item: ItemConst = syn::parse(item)?;
let rls_ident = item.ident.clone();
let register_rls_symbol = format!("__preinit__20_register_row_level_security_{rls_ident}");
Ok(quote! {
#item
const _: () = {
#[unsafe(export_name = #register_rls_symbol)]
extern "C" fn __register_client_visibility_filter() {
spacetimedb::rt::register_row_level_security(#rls_ident.sql_text())
}
};
})
})
}
const KNOWN_SETTINGS: &[&str] = &["CASE_CONVERSION_POLICY"];
#[proc_macro_attribute]
pub fn settings(args: StdTokenStream, item: StdTokenStream) -> StdTokenStream {
ok_or_compile_error(|| {
if !args.is_empty() {
return Err(syn::Error::new_spanned(
TokenStream::from(args),
"The `settings` attribute does not accept arguments",
));
}
let item: ItemConst = syn::parse(item)?;
let ident = &item.ident;
let ident_str = ident.to_string();
if !KNOWN_SETTINGS.contains(&ident_str.as_str()) {
return Err(syn::Error::new_spanned(
ident,
format!(
"unknown setting `{ident_str}`. Known settings: {}",
KNOWN_SETTINGS.join(", ")
),
));
}
let register_symbol = format!("__preinit__05_setting_{ident_str}");
let register_call = match ident_str.as_str() {
"CASE_CONVERSION_POLICY" => quote! {
spacetimedb::rt::register_case_conversion_policy(#ident)
},
_ => unreachable!("validated above"),
};
Ok(quote! {
#item
const _: () = {
#[unsafe(export_name = #register_symbol)]
extern "C" fn __register_setting() {
#register_call
}
};
})
})
}