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
120
121
122
123
124
125
126
127
128
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) {
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)*
_ => {}
}
} else if let Some(obj) = payload.as_object() {
for (key, val) in obj {
match key.as_str() {
#(#handlers)*
_ => {}
}
if let Some(v_str) = val.as_str() {
match v_str {
#(#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(),
}
}