diff --git a/src/error.rs b/src/error.rs index d2d9932..a8b5464 100644 --- a/src/error.rs +++ b/src/error.rs @@ -88,19 +88,23 @@ impl std::error::Error for ModuleError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } - - fn description(&self) -> &str { - "description() is deprecated; use Display" - } - - fn cause(&self) -> Option<&dyn std::error::Error> { - Some(self) - } } impl fmt::Display for ModuleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(format!("{:?}", self).as_str()) + match self { + ModuleError::NoError => write!(f, "NoError"), + ModuleError::TreeNotExist => write!(f, "Tree does not exist"), + ModuleError::TreeMessageError(msg) => write!(f, "Tree message error: {}", msg), + ModuleError::UnsupportedMethod => write!(f, "Unsupported method"), + ModuleError::InvalidType => write!(f, "Invalid type"), + ModuleError::LibLoadingError(msg) => write!(f, "Library loading error: {}", msg), + ModuleError::LinkError(msg) => write!(f, "Link error: {}", msg), + ModuleError::CryptError(msg) => write!(f, "Cryptography error: {}", msg), + ModuleError::DatabaseError(msg) => write!(f, "Database error: {}", msg), + ModuleError::SerdeJsonError(msg) => write!(f, "JSON error: {}", msg), + ModuleError::Error(msg) => write!(f, "{}", msg), + } } } diff --git a/src/tree/branch.rs b/src/tree/branch.rs index da5be98..8dd3d8b 100644 --- a/src/tree/branch.rs +++ b/src/tree/branch.rs @@ -103,6 +103,16 @@ impl Branch { json!(self.branch_type) } + /// Remove a child by name, returning the removed element + pub fn remove_child(&mut self, name: &str) -> Option> { + self.children.remove(name) + } + + /// Get a reference to the children map (for iteration) + pub fn children_mut(&mut self) -> &mut HashMap> { + &mut self.children + } + pub fn send_message(&mut self, target: Value, message: Value) -> Value { self.handle_local_message(target, message) } diff --git a/src/tree/component.rs b/src/tree/component.rs index 5c99666..c35ba12 100644 --- a/src/tree/component.rs +++ b/src/tree/component.rs @@ -6,6 +6,7 @@ use serde_json::{json, Value}; use crate::tree::{Branch, TreeElement}; +use crate::{error, info, warn}; /// Trait for component lifecycle management pub trait Component: Send + Sync { @@ -96,11 +97,13 @@ impl ComponentRegistry { let name = component.name().to_string(); if self.branch.get_child(&name).is_some() { + warn!("Component '{}' already registered", name); return Err(format!("Component '{}' already registered", name)); } let wrapper = ComponentWrapper::new(component); - self.branch.add_child(name, Box::new(wrapper)); + self.branch.add_child(name.clone(), Box::new(wrapper)); + info!("Component '{}' registered successfully", name); Ok(()) } @@ -116,8 +119,16 @@ impl ComponentRegistry { self.branch.children().contains_key(name) } - pub fn remove(&mut self, _name: &str) -> bool { - false + /// Remove a component from the registry by name. + /// Returns true if the component was found and removed. + pub fn remove(&mut self, name: &str) -> bool { + let removed = self.branch.remove_child(name).is_some(); + if removed { + info!("Component '{}' removed successfully", name); + } else { + warn!("Component '{}' not found for removal", name); + } + removed } pub fn list(&self) -> Vec { @@ -136,6 +147,7 @@ impl ComponentRegistry { if let Some(component) = self.branch.get_child(component_name) { component.send_message(json!(null), message) } else { + warn!("Component '{}' not found", component_name); let err_msg = format!("Component '{}' not found", component_name); json!({"error": err_msg}) } @@ -159,8 +171,47 @@ impl ComponentRegistry { .collect() } + /// Shutdown all registered components gracefully. + /// Returns a list of component names and their shutdown results. pub fn shutdown_all(&mut self) -> Vec<(String, Result<(), String>)> { - Vec::new() + info!("Shutting down all components"); + let names: Vec = self.branch.children().keys().cloned().collect(); + + let results: Vec<(String, Result<(), String>)> = names + .into_iter() + .filter_map(|name| { + // Try to send shutdown message to each component + if let Some(component) = self.branch.get_child(&name) { + let result = component.send_message(json!(null), json!({"method": "shutdown"})); + + // Check if shutdown was successful + let success = result + .get("success") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let err = if success { + info!("Component '{}' shutdown successfully", name); + Ok(()) + } else { + let error_msg = result + .get("error") + .and_then(|v| v.as_str()) + .unwrap_or("unknown error") + .to_string(); + error!("Component '{}' shutdown failed: {}", name, error_msg); + Err(error_msg) + }; + + Some((name, err)) + } else { + None + } + }) + .collect(); + + info!("All components shutdown complete"); + results } } diff --git a/ush-payload/src/protocols/http.rs b/ush-payload/src/protocols/http.rs index f123267..042ef55 100644 --- a/ush-payload/src/protocols/http.rs +++ b/ush-payload/src/protocols/http.rs @@ -212,10 +212,10 @@ mod tests { #[test] fn test_request_building() { - let server = HttpServer::new(Default::default()); + let proto = HttpProtocol::new(Default::default()); let body = r#"{"test": "data"}"#.as_bytes(); - let request = server.build_request(body); + let request = proto.encode(body).unwrap(); let request_str = String::from_utf8(request).unwrap(); assert!(request_str.contains("POST / HTTP/1.1")); diff --git a/ush-payload/src/tcp/client.rs b/ush-payload/src/tcp/client.rs index 41ce482..f65f804 100644 --- a/ush-payload/src/tcp/client.rs +++ b/ush-payload/src/tcp/client.rs @@ -17,6 +17,7 @@ use unshell::tree::component::Component; use unshell::tree::message::TreeMessage; use unshell::tree::symbols::*; use unshell::tree::{Branch, TreeElement}; +use unshell::{error, info}; /// TCP Client component with protocol stacking support. /// @@ -83,6 +84,7 @@ impl TcpClient { /// Connect to the configured address pub fn connect(&mut self) -> Result<(), String> { let addr = format!("{}:{}", self.config.address, self.config.port); + info!("[{}] Connecting to {}", self.name, addr); let stream = TcpStream::connect_timeout( &addr @@ -90,7 +92,10 @@ impl TcpClient { .map_err(|e| format!("Invalid address: {}", e))?, Duration::from_millis(self.config.timeout_ms), ) - .map_err(|e| format!("Connection failed: {}", e))?; + .map_err(|e| { + error!("[{}] Connection failed: {}", self.name, e); + format!("Connection failed: {}", e) + })?; stream .set_nonblocking(false) @@ -105,16 +110,19 @@ impl TcpClient { .map(|a| a.to_string()) .unwrap_or_default(); - self.status = ConnectionStatus::connected(remote, local); + self.status = ConnectionStatus::connected(remote.clone(), local); self.stream = Some(Arc::new(Mutex::new(stream))); + info!("[{}] Connected to {}", self.name, remote); Ok(()) } /// Disconnect from server pub fn disconnect(&mut self) -> Result<(), String> { + info!("[{}] Disconnecting", self.name); self.stream = None; self.status = ConnectionStatus::disconnected(); + info!("[{}] Disconnected", self.name); Ok(()) } @@ -305,7 +313,17 @@ impl Component for TcpClient { self.set_protocols(p)?; } - self.connect()?; + // Only auto-connect if explicitly requested in config + // This follows the philosophy that init() should configure, not connect + let auto_connect = config + .get("auto_connect") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + if auto_connect { + self.connect()?; + } + Ok(()) }