interstice_cli/
bindings.rs1use 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}