flowlang/builder/
api.rs

1//! This file will contain the logic for the `rebuild_rust_api` function,
2//! which generates a typed Rust API for all registered commands.
3
4use std::collections::HashSet;
5use std::fs::{create_dir_all, read_dir};
6
7use crate::DataStore;
8
9use super::util::{get_crate_info, get_project_top_level_path, lookup_rust_api_data_type, lookup_rust_api_ndata_method_suffix};
10
11/// Scans all libraries and commands to generate a typed Rust API.
12pub(crate) fn rebuild_rust_api() {
13    let store = DataStore::new();
14    let project_top_level_path = get_project_top_level_path();
15
16    let mut crates = HashSet::new();
17    let lib_entries_for_crates = read_dir("data")
18        .expect("Failed to read 'data' directory for API rebuilding.");
19    for dir_entry_result in lib_entries_for_crates {
20        let dir_entry = dir_entry_result.expect("Error reading a directory entry in 'data'.");
21        let lib_name = dir_entry.file_name().into_string().expect("Library name is not valid UTF-8.");
22        let lib_metadata = store.lib_info(&lib_name);
23        let (root_name, _) = get_crate_info(&lib_metadata);
24        if root_name != "." {
25            crates.insert(root_name);
26        }
27    }
28
29    let mut api_struct_init_str = "pub const fn new() -> api {\n    api {\n".to_string();
30    let mut api_struct_def_str = "pub struct api {\n".to_string();
31    let mut control_struct_defs_str = String::new();
32    let mut command_wrapper_struct_defs_str = String::new();
33    let mut impl_blocks_str = String::new();
34
35    let lib_entries = read_dir("data")
36        .expect("Failed to read 'data' directory for API rebuilding.");
37
38    for db_result in lib_entries {
39        let lib_entry = db_result.expect("Error reading library entry for API rebuilding.");
40        let lib_name = lib_entry.file_name().into_string()
41            .expect("Library name is not valid UTF-8 for API rebuilding.");
42
43        if store.exists(&lib_name, "controls") {
44            let safe_lib_name = lib_name.replace("-", "_");
45            api_struct_init_str.push_str(&format!("        {}: {} {{\n", safe_lib_name, safe_lib_name));
46            api_struct_def_str.push_str(&format!("    pub {}: {},\n", safe_lib_name, safe_lib_name));
47            control_struct_defs_str.push_str(&format!("pub struct {} {{\n", safe_lib_name));
48
49            let controls_data = store.get_data(&lib_name, "controls");
50            let list = controls_data.get_object("data").get_array("list");
51
52            for control_val in list.objects() {
53                let control = control_val.object();
54                let ctl_name = control.get_string("name");
55                let safe_ctl_name = ctl_name.replace("-", "_");
56                let ctl_id = control.get_string("id");
57
58                if store.exists(&lib_name, &ctl_id) {
59                    let struct_name = format!("{}_{}", safe_lib_name, safe_ctl_name);
60                    api_struct_init_str.push_str(&format!("            {}: {} {{}},\n", safe_ctl_name, struct_name));
61                    control_struct_defs_str.push_str(&format!("    pub {}: {},\n", safe_ctl_name, struct_name));
62                    command_wrapper_struct_defs_str.push_str(&format!("pub struct {} {{}}\n", struct_name));
63
64                    let ctldata = store.get_data(&lib_name, &ctl_id);
65                    let d = ctldata.get_object("data");
66
67                    if d.has("cmd") {
68                        let cmdlist = d.get_array("cmd");
69                        if cmdlist.len() > 0 {
70                            impl_blocks_str.push_str(&format!("impl {} {{\n", struct_name));
71                            for command_val in cmdlist.objects() {
72                                let command = command_val.object();
73                                let cmd_name = command.get_string("name");
74                                let safe_cmd_name = cmd_name.replace("-", "_");
75                                let cmd_id_in_control = command.get_string("id");
76
77                                if store.exists(&lib_name, &cmd_id_in_control) {
78                                    let meta_for_cmd_type = store.get_data(&lib_name, &cmd_id_in_control);
79                                    let data_for_cmd_type = meta_for_cmd_type.get_object("data");
80                                    
81                                    // Only proceed if the command metadata has a 'type' field.
82                                    if data_for_cmd_type.has("type") {
83                                        if data_for_cmd_type.get_string("type") == "rust" {
84                                            let rust_meta_file_id = data_for_cmd_type.get_string("rust");
85                                            let rust_cmd_actual_meta = store.get_data(&lib_name, &rust_meta_file_id).get_object("data");
86                                            let params_array = rust_cmd_actual_meta.get_array("params");
87                                            let rtype_str = rust_cmd_actual_meta.get_string("returntype");
88                                            let ntype_ret = lookup_rust_api_ndata_method_suffix(&rtype_str);
89                                            let rtype_rust = lookup_rust_api_data_type(&rtype_str);
90                                            
91                                            let mut params_str_for_fn_def = String::new();
92                                            let mut params_setup_str_for_body = String::new();
93
94                                            for param_val in params_array.objects() {
95                                                let param = param_val.object();
96                                                let pname = param.get_string("name");
97                                                let ptype = param.get_string("type");
98                                                let dtype = lookup_rust_api_data_type(&ptype);
99                                                let ntype = lookup_rust_api_ndata_method_suffix(&ptype);
100                                                params_str_for_fn_def.push_str(&format!(", {}: {}", pname, dtype));
101                                                let q = if dtype == "String" { "&" } else { "" };
102                                                let method_prefix = if ntype == "property" { "set" } else { "put" };
103                                                params_setup_str_for_body.push_str(&format!(
104                                                    "        d.{}_{}(\"{}\", {}{});\n",
105                                                    method_prefix, ntype, pname, q, pname
106                                                ));
107                                            }
108                                            
109                                            impl_blocks_str.push_str(&format!("    pub fn {} (&self{}", safe_cmd_name, params_str_for_fn_def));
110                                            impl_blocks_str.push_str(&format!(") -> {} {{\n", rtype_rust));
111                                            
112                                            let d_mut = if params_array.len() > 0 { "mut " } else { "" };
113                                            impl_blocks_str.push_str(&format!("        let {}d = ndata::dataobject::DataObject::new();\n", d_mut));
114                                            
115                                            impl_blocks_str.push_str(&params_setup_str_for_body);
116                                            impl_blocks_str.push_str(&format!(
117                                                "        flowlang::rustcmd::RustCmd::new(\"{}\").execute(d).expect(\"Rust command execution failed\").get_{}(\"a\")\n    }}\n",
118                                                rust_meta_file_id,
119                                                ntype_ret
120                                            ));
121                                        }
122                                    }
123                                }
124                            }
125                            impl_blocks_str.push_str("}\n");
126                        }
127                    }
128                }
129            }
130            api_struct_init_str.push_str("        },\n");
131            control_struct_defs_str.push_str("}\n");
132        }
133    }
134    api_struct_init_str.push_str("    }\n}\n");
135    api_struct_def_str.push_str("}");
136
137    // --- FIX for ndata private struct errors ---
138    let use_statements = r#"#![allow(non_camel_case_types, unused_variables)]
139use ndata::dataobject::DataObject;
140use ndata::dataarray::DataArray;
141use ndata::databytes::DataBytes;
142use ndata::data::Data;
143"#;
144    // --- End Fix ---
145
146    let final_api_code = format!(
147        "{}\n{}\n{}\n{}\n{}\n{}",
148        use_statements,
149        command_wrapper_struct_defs_str,
150        control_struct_defs_str,
151        api_struct_def_str,
152        api_struct_init_str,
153        impl_blocks_str
154    );
155
156    for crate_name in crates {
157        let api_file_path = project_top_level_path.join(crate_name).join("src").join("api.rs");
158        if let Some(parent_dir) = api_file_path.parent() {
159            create_dir_all(parent_dir).expect(&format!("Failed to create directory for api.rs: {:?}", parent_dir));
160        }
161        std::fs::write(&api_file_path, &final_api_code)
162            .expect(&format!("Unable to write API file to {:?}", api_file_path));
163    }
164}