1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Error, ImplItem, Item};
pub fn parse_live_component(item: TokenStream) -> TokenStream {
let parsed = syn::parse2::<Item>(item.clone());
match parsed {
Ok(Item::Impl(mut item_impl)) => {
let struct_name = &item_impl.self_ty;
let mut handlers = Vec::new();
let mut has_mount = false;
let mut is_mount_async = false;
let mut has_render = false;
for item in &mut item_impl.items {
if let ImplItem::Fn(method) = item {
let method_name = &method.sig.ident;
let method_name_str = method_name.to_string();
if method_name == "mount" {
has_mount = true;
is_mount_async = method.sig.asyncness.is_some();
}
if method_name == "render" {
has_render = true;
}
let has_live_event =
method.attrs.iter().any(|a| a.path().is_ident("live_event"));
if has_live_event {
// Assume self is first argument, check if it takes payload
let takes_payload = method.sig.inputs.len() > 1;
let is_async = method.sig.asyncness.is_some();
let call = if takes_payload {
if is_async {
quote! { self.#method_name(payload.clone()).await }
} else {
quote! { self.#method_name(payload.clone()) }
}
} else {
if is_async {
quote! { self.#method_name().await }
} else {
quote! { self.#method_name() }
}
};
handlers.push(quote! {
#method_name_str => { #call; },
});
method.attrs.retain(|a| !a.path().is_ident("live_event"));
}
}
}
if !has_render {
return Error::new_spanned(
item_impl,
"#[live_component] impl block must contain a `pub fn render(&self) -> String` method",
)
.to_compile_error();
}
let mount_tokens = if has_mount {
if is_mount_async {
quote! { async fn mount(&mut self) { self.mount().await; } }
} else {
quote! { async fn mount(&mut self) { self.mount(); } }
}
} else {
quote! {}
};
let expanded = quote! {
#item_impl
#[rullst::async_trait]
impl rullst::live::LiveComponent for #struct_name {
#mount_tokens
async fn handle_event(&mut self, payload: serde_json::Value) {
// Only the first present explicit event field selects a
// handler, and at most one handler runs per message.
// Other payload keys and values (for example form
// inputs typed by the user) never dispatch events.
let event_name_opt = payload.get("rullst_event")
.or_else(|| payload.get("action"))
.or_else(|| payload.get("event"))
.and_then(|v| v.as_str());
if let Some(event_name) = event_name_opt {
match event_name {
#(#handlers)*
_ => {}
}
}
}
fn render(&self) -> String {
self.render()
}
}
};
expanded
}
Ok(Item::Struct(item_struct)) => {
// Just return the struct untouched
quote! { #item_struct }
}
_ => Error::new_spanned(
item,
"#[live_component] can only be applied to structs or impl blocks",
)
.to_compile_error(),
}
}