mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
More consistency.
This commit is contained in:
+13
-9
@@ -88,19 +88,23 @@ impl std::error::Error for ModuleError {
|
|||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
None
|
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 {
|
impl fmt::Display for ModuleError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,16 @@ impl Branch {
|
|||||||
json!(self.branch_type)
|
json!(self.branch_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a child by name, returning the removed element
|
||||||
|
pub fn remove_child(&mut self, name: &str) -> Option<Box<dyn TreeElement>> {
|
||||||
|
self.children.remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the children map (for iteration)
|
||||||
|
pub fn children_mut(&mut self) -> &mut HashMap<String, Box<dyn TreeElement>> {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_message(&mut self, target: Value, message: Value) -> Value {
|
pub fn send_message(&mut self, target: Value, message: Value) -> Value {
|
||||||
self.handle_local_message(target, message)
|
self.handle_local_message(target, message)
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-4
@@ -6,6 +6,7 @@
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::tree::{Branch, TreeElement};
|
use crate::tree::{Branch, TreeElement};
|
||||||
|
use crate::{error, info, warn};
|
||||||
|
|
||||||
/// Trait for component lifecycle management
|
/// Trait for component lifecycle management
|
||||||
pub trait Component: Send + Sync {
|
pub trait Component: Send + Sync {
|
||||||
@@ -96,11 +97,13 @@ impl ComponentRegistry {
|
|||||||
let name = component.name().to_string();
|
let name = component.name().to_string();
|
||||||
|
|
||||||
if self.branch.get_child(&name).is_some() {
|
if self.branch.get_child(&name).is_some() {
|
||||||
|
warn!("Component '{}' already registered", name);
|
||||||
return Err(format!("Component '{}' already registered", name));
|
return Err(format!("Component '{}' already registered", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrapper = ComponentWrapper::new(component);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +119,16 @@ impl ComponentRegistry {
|
|||||||
self.branch.children().contains_key(name)
|
self.branch.children().contains_key(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, _name: &str) -> bool {
|
/// Remove a component from the registry by name.
|
||||||
false
|
/// 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<String> {
|
pub fn list(&self) -> Vec<String> {
|
||||||
@@ -136,6 +147,7 @@ impl ComponentRegistry {
|
|||||||
if let Some(component) = self.branch.get_child(component_name) {
|
if let Some(component) = self.branch.get_child(component_name) {
|
||||||
component.send_message(json!(null), message)
|
component.send_message(json!(null), message)
|
||||||
} else {
|
} else {
|
||||||
|
warn!("Component '{}' not found", component_name);
|
||||||
let err_msg = format!("Component '{}' not found", component_name);
|
let err_msg = format!("Component '{}' not found", component_name);
|
||||||
json!({"error": err_msg})
|
json!({"error": err_msg})
|
||||||
}
|
}
|
||||||
@@ -159,8 +171,47 @@ impl ComponentRegistry {
|
|||||||
.collect()
|
.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>)> {
|
pub fn shutdown_all(&mut self) -> Vec<(String, Result<(), String>)> {
|
||||||
Vec::new()
|
info!("Shutting down all components");
|
||||||
|
let names: Vec<String> = 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -212,10 +212,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_request_building() {
|
fn test_request_building() {
|
||||||
let server = HttpServer::new(Default::default());
|
let proto = HttpProtocol::new(Default::default());
|
||||||
|
|
||||||
let body = r#"{"test": "data"}"#.as_bytes();
|
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();
|
let request_str = String::from_utf8(request).unwrap();
|
||||||
assert!(request_str.contains("POST / HTTP/1.1"));
|
assert!(request_str.contains("POST / HTTP/1.1"));
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use unshell::tree::component::Component;
|
|||||||
use unshell::tree::message::TreeMessage;
|
use unshell::tree::message::TreeMessage;
|
||||||
use unshell::tree::symbols::*;
|
use unshell::tree::symbols::*;
|
||||||
use unshell::tree::{Branch, TreeElement};
|
use unshell::tree::{Branch, TreeElement};
|
||||||
|
use unshell::{error, info};
|
||||||
|
|
||||||
/// TCP Client component with protocol stacking support.
|
/// TCP Client component with protocol stacking support.
|
||||||
///
|
///
|
||||||
@@ -83,6 +84,7 @@ impl TcpClient {
|
|||||||
/// Connect to the configured address
|
/// Connect to the configured address
|
||||||
pub fn connect(&mut self) -> Result<(), String> {
|
pub fn connect(&mut self) -> Result<(), String> {
|
||||||
let addr = format!("{}:{}", self.config.address, self.config.port);
|
let addr = format!("{}:{}", self.config.address, self.config.port);
|
||||||
|
info!("[{}] Connecting to {}", self.name, addr);
|
||||||
|
|
||||||
let stream = TcpStream::connect_timeout(
|
let stream = TcpStream::connect_timeout(
|
||||||
&addr
|
&addr
|
||||||
@@ -90,7 +92,10 @@ impl TcpClient {
|
|||||||
.map_err(|e| format!("Invalid address: {}", e))?,
|
.map_err(|e| format!("Invalid address: {}", e))?,
|
||||||
Duration::from_millis(self.config.timeout_ms),
|
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
|
stream
|
||||||
.set_nonblocking(false)
|
.set_nonblocking(false)
|
||||||
@@ -105,16 +110,19 @@ impl TcpClient {
|
|||||||
.map(|a| a.to_string())
|
.map(|a| a.to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
self.status = ConnectionStatus::connected(remote, local);
|
self.status = ConnectionStatus::connected(remote.clone(), local);
|
||||||
self.stream = Some(Arc::new(Mutex::new(stream)));
|
self.stream = Some(Arc::new(Mutex::new(stream)));
|
||||||
|
|
||||||
|
info!("[{}] Connected to {}", self.name, remote);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disconnect from server
|
/// Disconnect from server
|
||||||
pub fn disconnect(&mut self) -> Result<(), String> {
|
pub fn disconnect(&mut self) -> Result<(), String> {
|
||||||
|
info!("[{}] Disconnecting", self.name);
|
||||||
self.stream = None;
|
self.stream = None;
|
||||||
self.status = ConnectionStatus::disconnected();
|
self.status = ConnectionStatus::disconnected();
|
||||||
|
info!("[{}] Disconnected", self.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +313,17 @@ impl Component for TcpClient {
|
|||||||
self.set_protocols(p)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user