1#![doc = include_str!("../README.md")]
2
3use configuration::{build_config_struct, parse_config};
4use database::build_database_struct;
5use enums::request_method::RequestMethod;
6use migrator::get_migrations;
7use parsed_action::parse_action;
8use parsed_route::parse_routes;
9use proc_macro2::{Span, TokenStream};
10use quote::quote;
11use syn::{parse_str, Ident, ItemFn, Path};
12use utils::to_snake_case;
13use workers::db_sync_worker::build_db_sync_worker_struct;
14
15mod configuration;
16mod database;
17pub mod enums;
18mod migrator;
19mod parsed_action;
20mod parsed_route;
21pub mod route_handler;
22pub mod router;
23pub mod traits;
24mod utils;
25pub mod workers;
26
27pub fn start_app(item: TokenStream) -> TokenStream {
28 let ast: ItemFn = syn::parse(item.into()).unwrap();
29 let stmts = &ast.block.stmts;
30
31 let database_struct = build_database_struct();
32 let db_sync_worker_struct = build_db_sync_worker_struct();
33
34 quote! {
35 use wasm_bindgen::prelude::*;
36 use rocal::rocal_core::traits::{Controller, View};
37
38 #[wasm_bindgen]
39 extern "C" {
40 #[wasm_bindgen(js_name = execSQL)]
41 fn exec_sql(db: &str, quer: &str) -> JsValue;
42 }
43
44 #[wasm_bindgen(start)]
45 pub async fn run() {
46 #(#stmts)*
47
48 let db_sync_worker = crate::DbSyncWorker::new(
49 "./js/db_sync_worker.js",
50 crate::ForceType::None
51 );
52 db_sync_worker.run();
53 }
54
55 #database_struct
56 #db_sync_worker_struct
57 }
58}
59
60pub fn build_route(item: TokenStream) -> TokenStream {
61 let routes = match parse_routes(item) {
62 Ok(routes) => routes,
63 Err(err) => return err.to_compile_error().into(),
64 };
65
66 let routes = routes.into_iter().map(|route| {
67 let controller = route
68 .get_controller()
69 .clone()
70 .expect("Controller should be here");
71 let controller_mod_name =
72 Ident::new(&to_snake_case(&controller.to_string()), Span::call_site());
73 let view = route.get_view().clone().expect("View should be here");
74 let view_mod_name = Ident::new(&to_snake_case(&view.to_string()), Span::call_site());
75 let method: Path = match route.get_method() {
76 Some(RequestMethod::Get) => {
77 parse_str("rocal::rocal_core::enums::request_method::RequestMethod::Get")
78 .expect("Failed to parse the enum")
79 }
80 Some(RequestMethod::Post) => {
81 parse_str("rocal::rocal_core::enums::request_method::RequestMethod::Post")
82 .expect("Failed to parse the enum")
83 }
84 _ => panic!("Method should be get or post"),
85 };
86 let path = route.get_path().clone().expect("Path should be here");
87 let action = route.get_action().clone().expect("Action shuold be here");
88
89 let ctrl = Ident::new(
90 &format!("{}_{}", "ctrl_", controller.to_string()),
91 Span::call_site(),
92 );
93
94 quote! {
95 let #ctrl = std::rc::Rc::new(crate::controllers::#controller_mod_name::#controller::new(
96 router.clone(),
97 crate::views::#view_mod_name::#view::new(router.clone()),
98 ));
99
100 router
101 .clone()
102 .borrow_mut()
103 .register(#method, #path, {
104 let #ctrl = std::rc::Rc::clone(&#ctrl);
105 Box::new(move |args| {
106 Box::pin({
107 let #ctrl = std::rc::Rc::clone(&#ctrl);
108 async move { #ctrl.#action(args).await }
109 })
110 })
111 });
112 }
113 });
114
115 quote! {
116 let router = std::rc::Rc::new(std::cell::RefCell::new(rocal::rocal_core::router::Router::new()));
117
118 #(#routes)*
119
120 let route_handler = rocal::rocal_core::route_handler::RouteHandler::new(router, None);
121 let route_handler = std::rc::Rc::new(route_handler);
122
123 route_handler.handle_route().await;
124
125 let handle_route_closure = {
126 let route_handler = std::rc::Rc::clone(&route_handler);
127 Closure::wrap(Box::new(move || {
128 let route_handler = std::rc::Rc::clone(&route_handler);
129 wasm_bindgen_futures::spawn_local(async move {
130 route_handler.handle_route().await;
131 });
132 }) as Box<dyn Fn()>)
133 };
134
135 web_sys::window()
136 .unwrap()
137 .add_event_listener_with_callback("popstate", handle_route_closure.as_ref().unchecked_ref())
138 .unwrap();
139 handle_route_closure.forget();
140 }
141}
142
143pub fn build_config(item: TokenStream) -> TokenStream {
144 let config_struct = build_config_struct();
145 let config = match parse_config(item) {
146 Ok(config) => config,
147 Err(err) => return err.to_compile_error().into(),
148 };
149
150 let app_id = config.get_app_id().clone().unwrap_or(String::new());
151 let sync_server_endpoint = config
152 .get_sync_server_endpoint()
153 .clone()
154 .unwrap_or(String::new());
155 let database_directory_name = config
156 .get_database_directory_name()
157 .clone()
158 .unwrap_or(String::new());
159 let database_file_name = config
160 .get_database_file_name()
161 .clone()
162 .unwrap_or(String::new());
163
164 quote! {
165 #config_struct
166
167 static CONFIG: std::sync::LazyLock<crate::Configuration> = std::sync::LazyLock::new(|| {
168 crate::Configuration::new(
169 #app_id.to_string(),
170 #sync_server_endpoint.to_string(),
171 std::sync::Arc::new(Database::new(
172 #database_directory_name.to_string(),
173 #database_file_name.to_string(),
174 )),
175 )
176 });
177 }
178}
179
180pub fn build_action(item: TokenStream) -> TokenStream {
181 let ast: ItemFn = syn::parse2(item).unwrap();
182
183 let parsed_action = match parse_action(&ast) {
184 Ok(action) => action,
185 Err(err) => return err.to_compile_error().into(),
186 };
187
188 let fn_name = parsed_action.get_name();
189
190 let build_args = parsed_action.get_args().iter().map(|arg| {
191 let name = arg.get_name();
192 let name_str = name.to_string();
193
194 let is_optional = arg.get_is_optional();
195
196 let mut result = if *is_optional {
197 quote! {
198 let #name = if let Some(#name) = args.get(#name_str) {
199 Some(#name.clone())
200 } else {
201 None
202 };
203 }
204 } else {
205 quote! {
206 let #name = args.get(#name_str).expect(&format!("{} is required", #name_str));
207 }
208 };
209
210 let ty = arg.get_ty();
211 let ty_str = ty.to_string();
212
213 result = if ty == "String" || ty == "str" {
214 quote!(#result)
215 } else {
216 if *is_optional {
217 quote! {
218 #result
219
220 let #name = if let Some(#name) = #name {
221 if let Ok(#name) = #name.parse::<#ty>() {
222 Some(#name)
223 } else {
224 None
225 }
226 } else {
227 None
228 }
229 }
230 } else {
231 quote! {
232 #result
233
234 let #name = #name.parse::<#ty>().expect(&format!("{} cannot be parsed as {}", #name_str, #ty_str));
235 }
236 }
237 };
238
239 result
240 });
241
242 let stmts = &ast.block.stmts;
243
244 quote! {
245 pub async fn #fn_name(&self, args: std::collections::HashMap<String, String>) {
246 #(#build_args)*
247
248 #(#stmts)*
249 }
250 }
251}
252
253pub fn run_migration(item: TokenStream) -> TokenStream {
254 let query = match get_migrations(&item) {
255 Ok(query) => query,
256 Err(err) => return err.to_compile_error().into(),
257 };
258
259 if !query.is_empty() {
260 quote! {
261 match CONFIG.get_database().exec(#query).await {
262 Ok(_) => (),
263 Err(err) => web_sys::console::error_1(&err),
264 }
265 }
266 } else {
267 quote!()
268 }
269}