Skip to main content

interstice_cli/
bindings.rs

1use crate::{node_client::fetch_node_schema, node_registry::NodeRegistry};
2use interstice_core::IntersticeError;
3use std::path::{Path, PathBuf};
4
5pub async fn handle_bindings_command(args: &[String]) -> Result<(), IntersticeError> {
6    if args.len() < 4 {
7        print_bindings_help();
8        return Ok(());
9    }
10
11    match args[2].as_str() {
12        "add" => match args[3].as_str() {
13            "module" => {
14                if args.len() < 6 {
15                    print_bindings_help();
16                    return Ok(());
17                }
18                let node_ref = &args[4];
19                let module_name = &args[5];
20                let project_path = args.get(6).map(Path::new).unwrap_or_else(|| Path::new("."));
21                let out_path = add_module_binding(node_ref, module_name, project_path).await?;
22                println!("Binding written to {}", out_path.display());
23            }
24            "node" => {
25                if args.len() < 5 {
26                    print_bindings_help();
27                    return Ok(());
28                }
29                let node_ref = &args[4];
30                let project_path = args.get(5).map(Path::new).unwrap_or_else(|| Path::new("."));
31                let out_path = add_node_binding(node_ref, project_path).await?;
32                println!("Binding written to {}", out_path.display());
33            }
34            _ => print_bindings_help(),
35        },
36        _ => print_bindings_help(),
37    }
38
39    Ok(())
40}
41
42fn print_bindings_help() {
43    println!("USAGE:");
44    println!("  interstice bindings add module <node> <module> [project_path]");
45    println!("  interstice bindings add node <node> [project_path]");
46}
47
48pub async fn add_module_binding(
49    node_ref: &str,
50    module_name: &str,
51    project_path: &Path,
52) -> Result<PathBuf, IntersticeError> {
53    let mut registry = NodeRegistry::load()?;
54    let address = registry
55        .resolve_address(node_ref)
56        .ok_or_else(|| IntersticeError::Internal(format!("Unknown node '{}'", node_ref)))?;
57    let node_name = registry
58        .get(node_ref)
59        .map(|n| n.name.clone())
60        .unwrap_or_else(|| node_ref.to_string());
61    let (schema, handshake) = fetch_node_schema(&address, &node_name).await?;
62    registry.set_last_seen(node_ref);
63    registry.set_node_id(node_ref, handshake.node_id);
64    registry.save()?;
65
66    let module = schema
67        .modules
68        .into_iter()
69        .find(|m| m.name == module_name)
70        .ok_or_else(|| {
71            IntersticeError::Internal(format!("Module '{}' not found in node schema", module_name))
72        })?;
73
74    let bindings_dir = project_path.join("src").join("bindings");
75    std::fs::create_dir_all(&bindings_dir).map_err(|err| {
76        IntersticeError::Internal(format!("Failed to create bindings directory: {err}"))
77    })?;
78    let out_path = bindings_dir.join(format!("{}.toml", module_name));
79    let contents = module.to_public().to_toml_string().map_err(|err| {
80        IntersticeError::Internal(format!("Failed to serialize module schema: {err}"))
81    })?;
82    std::fs::write(&out_path, contents).map_err(|err| {
83        IntersticeError::Internal(format!("Failed to write module binding: {err}"))
84    })?;
85    Ok(out_path)
86}
87
88pub async fn add_node_binding(
89    node_ref: &str,
90    project_path: &Path,
91) -> Result<PathBuf, IntersticeError> {
92    let mut registry = NodeRegistry::load()?;
93    let address = registry
94        .resolve_address(node_ref)
95        .ok_or_else(|| IntersticeError::Internal(format!("Unknown node '{}'", node_ref)))?;
96    let node_name = registry
97        .get(node_ref)
98        .map(|n| n.name.clone())
99        .unwrap_or_else(|| node_ref.to_string());
100    let (schema, handshake) = fetch_node_schema(&address, &node_name).await?;
101    registry.set_last_seen(node_ref);
102    registry.set_node_id(node_ref, handshake.node_id);
103    registry.save()?;
104
105    let bindings_dir = project_path.join("src").join("bindings");
106    std::fs::create_dir_all(&bindings_dir).map_err(|err| {
107        IntersticeError::Internal(format!("Failed to create bindings directory: {err}"))
108    })?;
109    let file_name = sanitize_filename(&node_name);
110    let out_path = bindings_dir.join(format!("node_{}.toml", file_name));
111    let contents = schema.to_public().to_toml_string().map_err(|err| {
112        IntersticeError::Internal(format!("Failed to serialize node schema: {err}"))
113    })?;
114    std::fs::write(&out_path, contents)
115        .map_err(|err| IntersticeError::Internal(format!("Failed to write node binding: {err}")))?;
116    Ok(out_path)
117}
118
119fn sanitize_filename(value: &str) -> String {
120    value
121        .chars()
122        .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
123        .collect()
124}