noapi_functions/
build_functions.rs

1use crate::{
2    config::{Config, PackageManager},
3    js_commands::{bun_build, deno_build, npm_build, pnpm_build, yarn_build},
4    struct_extractor::extract_struct_def,
5};
6use regex::Regex;
7use std::{fs, io::Write, path::Path, process::ExitStatus};
8
9pub fn build_frontend() -> Result<(), Box<dyn std::error::Error>> {
10    let package_manager = Config::from_file().unwrap().package_manager;
11    let status: ExitStatus;
12
13    match package_manager {
14        PackageManager::Bun => {
15            status = bun_build()?;
16        }
17        PackageManager::Deno => {
18            status = deno_build()?;
19        }
20        PackageManager::NPM => {
21            status = npm_build()?;
22        }
23        PackageManager::PNPM => {
24            status = pnpm_build()?;
25        }
26        PackageManager::YARN => {
27            status = yarn_build()?;
28        }
29    }
30
31    if !status.success() {
32        return Err(format!(
33            "Failed to build the frontend, exit code: {:?}",
34            status.code()
35        )
36        .into());
37    }
38
39    Ok(())
40}
41
42pub fn rust_functions_to_axum_handlers(input_path: &str, output_path: &str) {
43    let content = fs::read_to_string(input_path).expect("Failed to read Rust file");
44
45    let mut file = fs::File::create(format!("{}/functions.rs", output_path))
46        .expect("Failed to create output Rust file");
47    file.write_all(content.as_bytes())
48        .expect("Failed to write to output Rust file");
49
50    // Regex to extract function names and parameters
51    let function_regex = Regex::new(r"fn\s+(\w+)\s*\(([^)]*)\)").expect("Invalid function regex");
52
53    let mut axum_content = String::from(
54        "mod functions;\n\nuse std::fs;\nuse axum::{Router, routing::{get,post}, response::{Html, IntoResponse}, Json};\nuse serde_json::json;\nuse tower_http::services::ServeDir;\nuse tower_livereload::LiveReloadLayer;\n\n",
55    );
56
57    let mut routes = Vec::new();
58
59    // Extract functions and generate Axum handlers
60    for cap in function_regex.captures_iter(&content) {
61        let function_name = &cap[1];
62        let params = &cap[2];
63
64        let mut param_names = Vec::new();
65        let mut param_types = Vec::new();
66
67        params
68            .split(',')
69            .filter(|p| !p.trim().is_empty())
70            .for_each(|p| {
71                let parts: Vec<&str> = p.trim().split(':').collect();
72                if parts.len() == 2 {
73                    let param_name = parts[0].trim();
74                    let param_type = parts[1].trim();
75                    param_names.push(param_name);
76                    param_types.push(param_type);
77                }
78            });
79
80        // Create an Axum handler for the function
81        let handler_name = format!("{}_handler", function_name);
82        let handler = format!(
83            "async fn {}(Json(({})): Json<({})>) -> impl IntoResponse {{\n    let response = functions::{}({});\n    Json(json!(response))\n}}\n\n",
84            handler_name,param_names.join(","),param_types.join(","), function_name,param_names.join(","),
85        );
86
87        axum_content.push_str(&handler);
88        routes.push(format!(
89            "    .route(\"/{0}\", post({0}_handler))",
90            function_name
91        ));
92    }
93
94    // Create Axum router
95    axum_content
96        .push_str("\npub fn create_router() -> Router {\n    let mut router = Router::new()\n");
97    axum_content.push_str(&routes.join("\n"));
98    axum_content.push_str(";\n");
99    axum_content.push_str("router = router.nest_service(
100                        \"/assets\",
101                        ServeDir::new(&format!(\"{}/{}\", env!(\"CARGO_MANIFEST_DIR\"), \"src/static/assets\")),
102                    );\n");
103    axum_content.push_str(
104        "
105       router = router.fallback(get({
106        match fs::read_to_string(format!(
107            \"{}/{}\",
108            env!(\"CARGO_MANIFEST_DIR\"),
109            \"src/static/index.html\"
110        )) {
111            Ok(contents) => Html(contents),
112            Err(_) => Html(\"Error reading file\".to_string()),
113        }
114    }));\n",
115    );
116    axum_content.push_str("router = router.layer(LiveReloadLayer::new());\n");
117    axum_content.push_str("return router;");
118    axum_content.push_str("\n}\n");
119
120    // Write to output file
121    let mut file = fs::File::create(format!("{}/mod.rs", output_path))
122        .expect("Failed to create output Rust file");
123    file.write_all(axum_content.as_bytes())
124        .expect("Failed to write to output Rust file");
125}
126
127pub fn rust_to_typescript_type(
128    rust_type: &str,
129    custom_types: &Vec<String>,
130    content: &str,
131    project_name: &str,
132) -> String {
133    match rust_type {
134        "i32" | "i64" | "u32" | "u64" | "f32" | "f64" => "number".to_string(),
135        "String" | "&str" => "string".to_string(),
136        "bool" => "boolean".to_string(),
137        "Vec" => "Array<any>".to_string(),
138        "Option" => "any | null".to_string(),
139        _ if custom_types.contains(&rust_type.to_string()) => rust_type.to_string(),
140        _ => match extract_struct_def(rust_type.to_string(), content, project_name) {
141            Some(ts_type) => ts_type,
142            None => "any".to_string(),
143        },
144    }
145}
146
147pub fn rust_to_typescript_functons<P: AsRef<Path>>(
148    input_path: P,
149    output_path: P,
150    project_name: &str,
151) {
152    let content = fs::read_to_string(input_path).expect("Failed to read Rust file");
153    let struct_regex = Regex::new(r"struct\s+(\w+)\s*\{([^}]*)\}").expect("Invalid struct regex");
154    let function_regex =
155        Regex::new(r"fn\s+(\w+)\s*\(([^)]*)\)\s*->\s*([^{]+)").expect("Invalid function regex");
156
157    let mut ts_content = String::from("import axios from 'axios'\n\n");
158    let mut custom_types = Vec::new();
159
160    for cap in struct_regex.captures_iter(&content) {
161        let struct_name = &cap[1];
162        let fields = &cap[2];
163        custom_types.push(struct_name.to_string());
164
165        let ts_fields: Vec<String> = fields
166            .lines()
167            .filter(|line| !line.trim().is_empty())
168            .map(|line| {
169                let parts: Vec<&str> = line.trim().split(':').collect();
170                if parts.len() == 2 {
171                    let field_name = parts[0].trim();
172                    let field_type = rust_to_typescript_type(
173                        parts[1].trim().trim_end_matches(','),
174                        &custom_types,
175                        &content,
176                        project_name,
177                    );
178                    format!("    {}: {};", field_name, field_type)
179                } else {
180                    String::new()
181                }
182            })
183            .collect();
184
185        ts_content.push_str(&format!(
186            "export interface {} {{\n{}\n}}\n\n",
187            struct_name,
188            ts_fields.join("\n").replace("pub", "")
189        ));
190    }
191
192    // Extract and generate TypeScript function signatures
193    for cap in function_regex.captures_iter(&content) {
194        let function_name = &cap[1];
195        let params = &cap[2];
196        let return_type = &cap[3].trim();
197
198        // Parse parameters
199
200        let mut ts_params_without_types: Vec<String> = vec![];
201        let ts_params: Vec<String> = params
202            .split(',')
203            .filter(|p| !p.trim().is_empty())
204            .map(|p| {
205                let parts: Vec<&str> = p.trim().split(':').collect();
206                if parts.len() == 2 {
207                    let param_name = parts[0].trim();
208                    match parts[1].trim() {
209                        "i32" | "i64" | "u32" | "u64" => ts_params_without_types.push(format!(
210                            "parseInt({}.toString(), 10)",
211                            param_name.to_string()
212                        )),
213                        "f32" | "f64" => ts_params_without_types
214                            .push(format!("parseFloat({}.toString())", param_name.to_string())),
215                        _ => ts_params_without_types.push(param_name.to_string()),
216                    }
217                    let param_type = rust_to_typescript_type(
218                        parts[1].trim(),
219                        &custom_types,
220                        &content,
221                        project_name,
222                    );
223                    format!("{}: {}", param_name, param_type)
224                } else {
225                    "unknown: any".to_string()
226                }
227            })
228            .collect();
229
230        // Convert return type
231        let ts_return_type =
232            rust_to_typescript_type(return_type, &custom_types, &content, project_name);
233
234        // Add to TypeScript content
235        ts_content.push_str(&format!(
236            "export async function {}({}): Promise<{}>{{\nlet base_url = window.origin;\nlet data:any = [{}];\n let response = await axios.post(`${{base_url}}/{}`, data);\n return response.data;\n}}\n\n",
237            function_name,
238            ts_params.join(", "),
239            ts_return_type,
240            ts_params_without_types.join(", "),
241            function_name,
242        ));
243    }
244
245    // Write to TypeScript file
246    let mut ts_file = fs::File::create(output_path).expect("Failed to create TypeScript file");
247    ts_file
248        .write_all(ts_content.as_bytes())
249        .expect("Failed to write to TypeScript file");
250}