From 47344c71f0cab33aa5ce94de6cebcfa065a4c021 Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Fri, 29 Aug 2025 12:45:47 +0200 Subject: [PATCH 1/2] added more verbose error messages to RPC responses --- src/app/rpc.rs | 272 ++++++++++++++++++++++++++++++++-------- zinit-client/src/lib.rs | 78 ++++++++++-- 2 files changed, 289 insertions(+), 61 deletions(-) diff --git a/src/app/rpc.rs b/src/app/rpc.rs index 199e709..c5e2366 100644 --- a/src/app/rpc.rs +++ b/src/app/rpc.rs @@ -5,16 +5,23 @@ use jsonrpsee::core::{RpcResult, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::{ErrorCode, ErrorObjectOwned}; use jsonrpsee::PendingSubscriptionSink; -use serde_json::{Map, Value}; +use serde_json::{json, Map, Value}; use std::collections::HashMap; use std::str::FromStr; use tokio_stream::StreamExt; +use anyhow::Error as AnyError; +use crate::zinit::errors::ZInitError; + use super::api::Api; // Custom error codes for Zinit const SERVICE_NOT_FOUND: i32 = -32000; +const SERVICE_ALREADY_MONITORED: i32 = -32001; const SERVICE_IS_UP: i32 = -32002; +const SERVICE_IS_DOWN: i32 = -32003; +const INVALID_SIGNAL: i32 = -32004; +const CONFIG_ERROR: i32 = -32005; const SHUTTING_DOWN: i32 = -32006; const SERVICE_ALREADY_EXISTS: i32 = -32007; const SERVICE_FILE_ERROR: i32 = -32008; @@ -22,6 +29,106 @@ const SERVICE_FILE_ERROR: i32 = -32008; // Include the OpenRPC specification const OPENRPC_SPEC: &str = include_str!("../../openrpc.json"); +fn code_name_from(code: i32) -> &'static str { + match code { + SERVICE_NOT_FOUND => "ServiceNotFound", + SERVICE_ALREADY_MONITORED => "ServiceAlreadyMonitored", + SERVICE_IS_UP => "ServiceIsUp", + SERVICE_IS_DOWN => "ServiceIsDown", + INVALID_SIGNAL => "InvalidSignal", + CONFIG_ERROR => "ConfigError", + SHUTTING_DOWN => "ShuttingDown", + SERVICE_ALREADY_EXISTS => "ServiceAlreadyExists", + SERVICE_FILE_ERROR => "ServiceFileError", + _ => "InternalError", + } +} + +// Map ZInit/anyhow error -> JSON-RPC ErrorObjectOwned with structured details. +fn zinit_err_to_rpc(err: AnyError, action: &str, service: Option<&str>, default_code: i32) -> ErrorObjectOwned { + // Choose code/name/message from domain errors when possible + let (code, code_name, top_msg) = match err.downcast_ref::() { + Some(ZInitError::UnknownService { name }) => (SERVICE_NOT_FOUND, "ServiceNotFound", format!("service '{}' not found", name)), + Some(ZInitError::ServiceAlreadyMonitored { name }) => (SERVICE_ALREADY_MONITORED, "ServiceAlreadyMonitored", format!("service '{}' already monitored", name)), + Some(ZInitError::ServiceIsUp { name }) => (SERVICE_IS_UP, "ServiceIsUp", format!("service '{}' is up", name)), + Some(ZInitError::ServiceIsDown { name }) => (SERVICE_IS_DOWN, "ServiceIsDown", format!("service '{}' is down", name)), + Some(ZInitError::ShuttingDown) => (SHUTTING_DOWN, "ShuttingDown", "system is shutting down".to_string()), + Some(ZInitError::InvalidStateTransition { message }) => (-32009, "InvalidStateTransition", message.clone()), + Some(ZInitError::DependencyError { message }) => (-32010, "DependencyError", message.clone()), + Some(ZInitError::ProcessError { message }) => (-32011, "ProcessError", message.clone()), + None => (default_code, code_name_from(default_code), err.to_string()), + }; + + // Build cause_chain from std::error::Error sources. + let mut cause_chain: Vec = Vec::new(); + let mut src = err.source(); + while let Some(s) = src { + cause_chain.push(s.to_string()); + src = s.source(); + } + + let retryable = matches!(code, SERVICE_ALREADY_MONITORED | SERVICE_IS_DOWN); + + let data = json!({ + "code_name": code_name, + "service": service, + "action": action, + "retryable": retryable, + "cause_chain": if cause_chain.is_empty() { serde_json::Value::Null } else { serde_json::Value::Array(cause_chain.into_iter().map(serde_json::Value::String).collect()) }, + "hint": match code { + SERVICE_NOT_FOUND => "Ensure the service is monitored or the name is correct", + SERVICE_ALREADY_MONITORED => "The service is already monitored; call service_forget or control it with start/stop", + SERVICE_IS_UP => "Service is already up; use service_stop or restart", + SERVICE_IS_DOWN => "Service is down; start it before sending signals or requesting stats", + SHUTTING_DOWN => "Wait for shutdown to complete or avoid mutating operations during shutdown", + SERVICE_FILE_ERROR => "Check filesystem permissions, disk space, and path", + _ => "Enable verbose logs and inspect cause_chain for details" + } + }); + + let message = format!("{}: {}", action, top_msg); + ErrorObjectOwned::owned(code, message, Some(data)) +} + +fn invalid_service_name_error(action: &str, name: &str) -> ErrorObjectOwned { + ErrorObjectOwned::owned( + ErrorCode::InvalidParams.code(), + format!("{action}: invalid service name '{}'", name), + Some(json!({ + "code_name": "InvalidServiceName", + "action": action, + "service": name, + "hint": "Name must not contain '/', '\\\\' or '.'" + })), + ) +} + +fn invalid_signal_error(signal: &str) -> ErrorObjectOwned { + ErrorObjectOwned::owned( + INVALID_SIGNAL, + format!("parsing signal: invalid signal '{}'", signal), + Some(json!({ + "code_name": "InvalidSignal", + "action": "parsing signal", + "signal": signal, + "hint": "Use a valid POSIX signal like SIGTERM or SIGKILL" + })), + ) +} + +fn service_file_error(action: &str, name: &str, detail: &str) -> ErrorObjectOwned { + ErrorObjectOwned::owned( + SERVICE_FILE_ERROR, + format!("{action}: {detail}"), + Some(json!({ + "code_name": "ServiceFileError", + "action": action, + "service": name, + "hint": "Check permissions and filesystem availability" + })), + ) +} + /// RPC methods for discovery. #[rpc(server, client)] pub trait ZinitRpcApi { @@ -96,7 +203,7 @@ impl ZinitServiceApiServer for Api { .zinit .list() .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + .map_err(|e| zinit_err_to_rpc(e, "listing services", None, ErrorCode::InternalError.code()))?; let mut map: HashMap = HashMap::new(); for service in services { @@ -104,7 +211,7 @@ impl ZinitServiceApiServer for Api { .zinit .status(&service) .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + .map_err(|e| zinit_err_to_rpc(e, "getting status", Some(&service), ErrorCode::InternalError.code()))?; map.insert(service, format!("{:?}", state.state)); } Ok(map) @@ -115,7 +222,7 @@ impl ZinitServiceApiServer for Api { .zinit .status(&name) .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + .map_err(|e| zinit_err_to_rpc(e, "getting status", Some(&name), ErrorCode::InternalError.code()))?; let result = Status { name: name.clone(), @@ -125,11 +232,11 @@ impl ZinitServiceApiServer for Api { after: { let mut after = HashMap::new(); for service in status.service.after { - let status = match self.zinit.status(&service).await { + let dep_state = match self.zinit.status(&service).await { Ok(dep) => dep.state, Err(_) => crate::zinit::State::Unknown, }; - after.insert(service, format!("{:?}", status)); + after.insert(service, format!("{:?}", dep_state)); } after }, @@ -140,46 +247,59 @@ impl ZinitServiceApiServer for Api { async fn start(&self, name: String) -> RpcResult<()> { self.zinit - .start(name) + .start(name.clone()) .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::ServerError(SERVICE_IS_UP))) + .map_err(|e| zinit_err_to_rpc(e, "starting service", Some(&name), ErrorCode::InternalError.code())) } async fn stop(&self, name: String) -> RpcResult<()> { self.zinit - .stop(name) + .stop(name.clone()) .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError)) + .map_err(|e| zinit_err_to_rpc(e, "stopping service", Some(&name), ErrorCode::InternalError.code())) } async fn monitor(&self, name: String) -> RpcResult<()> { - if let Ok((name_str, service)) = config::load(format!("{}.yaml", name)) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError)) - { - self.zinit - .monitor(name_str, service) - .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError)) - } else { - Err(ErrorObjectOwned::from(ErrorCode::InternalError)) + // Validate service name + if name.contains('/') || name.contains('\\') || name.contains('.') { + return Err(invalid_service_name_error("monitoring service", &name)); } + + let (name_str, service) = config::load(format!("{}.yaml", name)) + .map_err(|e| { + ErrorObjectOwned::owned( + CONFIG_ERROR, + format!("monitoring service: failed to load '{}.yaml'", name), + Some(json!({ + "code_name": "ConfigError", + "action": "loading service config", + "service": name, + "hint": "Verify the YAML file exists and is valid" + })), + ) + })?; + + self.zinit + .monitor(name_str.clone(), service) + .await + .map_err(|e| zinit_err_to_rpc(e, "monitoring service", Some(&name_str), ErrorCode::InternalError.code())) } async fn forget(&self, name: String) -> RpcResult<()> { self.zinit - .forget(name) + .forget(name.clone()) .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError)) + .map_err(|e| zinit_err_to_rpc(e, "forgetting service", Some(&name), ErrorCode::InternalError.code())) } async fn kill(&self, name: String, signal: String) -> RpcResult<()> { if let Ok(sig) = nix::sys::signal::Signal::from_str(&signal.to_uppercase()) { self.zinit - .kill(name, sig) + .kill(name.clone(), sig) .await - .map_err(|_e| ErrorObjectOwned::from(ErrorCode::InternalError)) + .map_err(|e| zinit_err_to_rpc(e, "sending signal", Some(&name), ErrorCode::InternalError.code())) } else { - Err(ErrorObjectOwned::from(ErrorCode::InternalError)) + Err(invalid_signal_error(&signal)) } } @@ -190,29 +310,46 @@ impl ZinitServiceApiServer for Api { // Validate service name (no path traversal, valid characters) if name.contains('/') || name.contains('\\') || name.contains('.') { - return Err(ErrorObjectOwned::from(ErrorCode::InternalError)); + return Err(invalid_service_name_error("creating service", &name)); } // Construct the file path - let file_path = PathBuf::from(format!("{}.yaml", name)); + let file_path = PathBuf::from(format!("{}.yaml", &name)); // Check if the service file already exists if file_path.exists() { - return Err(ErrorObjectOwned::from(ErrorCode::ServerError( + return Err(ErrorObjectOwned::owned( SERVICE_ALREADY_EXISTS, - ))); + format!("creating service: Service '{}' already exists", name), + Some(json!({ + "code_name": "ServiceAlreadyExists", + "action": "creating service file", + "service": name, + "hint": "Use a different name or delete the existing service first" + })), + )); } // Convert the JSON content to YAML - let yaml_content = serde_yaml::to_string(&content) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + let yaml_content = serde_yaml::to_string(&content).map_err(|e| { + ErrorObjectOwned::owned( + CONFIG_ERROR, + "creating service: failed to convert content to YAML".to_string(), + Some(json!({ + "code_name": "ConfigError", + "action": "serializing service config", + "service": name, + "hint": "Ensure content is valid according to schema" + })), + ) + })?; // Write the YAML content to the file let mut file = fs::File::create(&file_path) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::ServerError(SERVICE_FILE_ERROR)))?; + .map_err(|_| service_file_error("creating service file", &name, "failed to create service file"))?; file.write_all(yaml_content.as_bytes()) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::ServerError(SERVICE_FILE_ERROR)))?; + .map_err(|_| service_file_error("writing service file", &name, "failed to write service file"))?; Ok(format!("Service '{}' created successfully", name)) } @@ -223,22 +360,29 @@ impl ZinitServiceApiServer for Api { // Validate service name (no path traversal, valid characters) if name.contains('/') || name.contains('\\') || name.contains('.') { - return Err(ErrorObjectOwned::from(ErrorCode::InternalError)); + return Err(invalid_service_name_error("deleting service", &name)); } // Construct the file path - let file_path = PathBuf::from(format!("{}.yaml", name)); + let file_path = PathBuf::from(format!("{}.yaml", &name)); // Check if the service file exists if !file_path.exists() { - return Err(ErrorObjectOwned::from(ErrorCode::ServerError( + return Err(ErrorObjectOwned::owned( SERVICE_NOT_FOUND, - ))); + format!("deleting service: Service '{}' not found", name), + Some(json!({ + "code_name": "ServiceNotFound", + "action": "deleting service file", + "service": name, + "hint": "Ensure the service file exists" + })), + )); } // Delete the file fs::remove_file(&file_path) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::ServerError(SERVICE_FILE_ERROR)))?; + .map_err(|_| service_file_error("deleting service file", &name, "failed to delete service file"))?; Ok(format!("Service '{}' deleted successfully", name)) } @@ -249,30 +393,56 @@ impl ZinitServiceApiServer for Api { // Validate service name (no path traversal, valid characters) if name.contains('/') || name.contains('\\') || name.contains('.') { - return Err(ErrorObjectOwned::from(ErrorCode::InternalError)); + return Err(invalid_service_name_error("getting service", &name)); } // Construct the file path - let file_path = PathBuf::from(format!("{}.yaml", name)); + let file_path = PathBuf::from(format!("{}.yaml", &name)); // Check if the service file exists if !file_path.exists() { - return Err(ErrorObjectOwned::from(ErrorCode::ServerError( + return Err(ErrorObjectOwned::owned( SERVICE_NOT_FOUND, - ))); + format!("getting service: Service '{}' not found", name), + Some(json!({ + "code_name": "ServiceNotFound", + "action": "reading service file", + "service": name, + "hint": "Ensure the service file exists" + })), + )); } // Read the file content let yaml_content = fs::read_to_string(&file_path) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::ServerError(SERVICE_FILE_ERROR)))?; + .map_err(|_| service_file_error("reading service file", &name, "failed to read service file"))?; // Parse YAML to JSON - let yaml_value: serde_yaml::Value = serde_yaml::from_str(&yaml_content) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + let yaml_value: serde_yaml::Value = serde_yaml::from_str(&yaml_content).map_err(|_| { + ErrorObjectOwned::owned( + CONFIG_ERROR, + "getting service: failed to parse YAML".to_string(), + Some(json!({ + "code_name": "ConfigError", + "action": "parsing service file", + "service": name, + "hint": "Ensure the YAML is valid" + })), + ) + })?; // Convert YAML value to JSON value - let json_value = serde_json::to_value(yaml_value) - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + let json_value = serde_json::to_value(yaml_value).map_err(|_| { + ErrorObjectOwned::owned( + CONFIG_ERROR, + "getting service: failed to convert YAML to JSON".to_string(), + Some(json!({ + "code_name": "ConfigError", + "action": "converting YAML to JSON", + "service": name + })), + ) + })?; Ok(json_value) } @@ -282,7 +452,7 @@ impl ZinitServiceApiServer for Api { .zinit .stats(&name) .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError))?; + .map_err(|e| zinit_err_to_rpc(e, "collecting stats", Some(&name), ErrorCode::InternalError.code()))?; let result = Stats { name: name.clone(), @@ -330,21 +500,21 @@ impl ZinitSystemApiServer for Api { self.zinit .shutdown() .await - .map_err(|_e| ErrorObjectOwned::from(ErrorCode::ServerError(SHUTTING_DOWN))) + .map_err(|e| zinit_err_to_rpc(e, "system shutdown", None, SHUTTING_DOWN)) } async fn reboot(&self) -> RpcResult<()> { self.zinit .reboot() .await - .map_err(|_| ErrorObjectOwned::from(ErrorCode::InternalError)) + .map_err(|e| zinit_err_to_rpc(e, "system reboot", None, ErrorCode::InternalError.code())) } async fn start_http_server(&self, address: String) -> RpcResult { // Call the method from the API implementation match crate::app::api::Api::start_http_server(self, address).await { Ok(result) => Ok(result), - Err(_) => Err(ErrorObjectOwned::from(ErrorCode::InternalError)), + Err(e) => Err(zinit_err_to_rpc(AnyError::from(e), "starting http server", None, ErrorCode::InternalError.code())), } } @@ -352,7 +522,7 @@ impl ZinitSystemApiServer for Api { // Call the method from the API implementation match crate::app::api::Api::stop_http_server(self).await { Ok(_) => Ok(()), - Err(_) => Err(ErrorObjectOwned::from(ErrorCode::InternalError)), + Err(e) => Err(zinit_err_to_rpc(AnyError::from(e), "stopping http server", None, ErrorCode::InternalError.code())), } } } diff --git a/zinit-client/src/lib.rs b/zinit-client/src/lib.rs index b01fef6..0d6b370 100644 --- a/zinit-client/src/lib.rs +++ b/zinit-client/src/lib.rs @@ -21,9 +21,21 @@ pub enum ClientError { #[error("service not found: {0}")] ServiceNotFound(String), + #[error("service already monitored: {0}")] + ServiceAlreadyMonitored(String), + #[error("service is already up: {0}")] ServiceIsUp(String), + #[error("service is down: {0}")] + ServiceIsDown(String), + + #[error("invalid signal: {0}")] + InvalidSignal(String), + + #[error("config error: {0}")] + ConfigError(String), + #[error("system is shutting down")] ShuttingDown, @@ -42,16 +54,62 @@ pub enum ClientError { impl From for ClientError { fn from(err: RpcError) -> Self { - // Parse the error code if available - if let RpcError::Call(err) = &err { - match err.code() { - -32000 => return ClientError::ServiceNotFound(err.message().to_string()), - -32002 => return ClientError::ServiceIsUp(err.message().to_string()), - -32006 => return ClientError::ShuttingDown, - -32007 => return ClientError::ServiceAlreadyExists(err.message().to_string()), - -32008 => return ClientError::ServiceFileError(err.message().to_string()), - _ => {} - } + if let RpcError::Call(call) = &err { + let code = call.code(); + let message = call.message().to_string(); + + // Try to parse structured data payload if present + let details: Option = call + .data() + .and_then(|raw| serde_json::from_str(raw.get()).ok()); + + let code_name = details + .as_ref() + .and_then(|d| d.get("code_name")) + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + + let service = details + .as_ref() + .and_then(|d| d.get("service")) + .and_then(|v| v.as_str()); + + let action = details + .as_ref() + .and_then(|d| d.get("action")) + .and_then(|v| v.as_str()); + + let hint = details + .as_ref() + .and_then(|d| d.get("hint")) + .and_then(|v| v.as_str()); + + let chain = details.as_ref().and_then(|d| d.get("cause_chain")).cloned(); + + let human = match (service, action, hint) { + (Some(s), Some(a), Some(h)) => { + format!("{}[{}]: {} while {} '{}'. Hint: {}. Details: {:?}", code_name, code, message, a, s, h, chain) + } + (Some(s), Some(a), None) => { + format!("{}[{}]: {} while {} '{}'. Details: {:?}", code_name, code, message, a, s, chain) + } + _ => { + format!("{}[{}]: {}. Details: {:?}", code_name, code, message, chain) + } + }; + + return match code { + -32000 => ClientError::ServiceNotFound(human), + -32001 => ClientError::ServiceAlreadyMonitored(human), + -32002 => ClientError::ServiceIsUp(human), + -32003 => ClientError::ServiceIsDown(human), + -32004 => ClientError::InvalidSignal(human), + -32005 => ClientError::ConfigError(human), + -32006 => ClientError::ShuttingDown, + -32007 => ClientError::ServiceAlreadyExists(human), + -32008 => ClientError::ServiceFileError(human), + _ => ClientError::RpcError(human), + }; } match err { From eb8207300b46cff0155a64ede2789d6bd04adb75 Mon Sep 17 00:00:00 2001 From: Maxime Van Hees Date: Fri, 29 Aug 2025 12:47:30 +0200 Subject: [PATCH 2/2] fixed formatting --- src/app/rpc.rs | 229 +++++++++++++++++++++++++++------------- zinit-client/src/lib.rs | 10 +- 2 files changed, 164 insertions(+), 75 deletions(-) diff --git a/src/app/rpc.rs b/src/app/rpc.rs index c5e2366..f23ccda 100644 --- a/src/app/rpc.rs +++ b/src/app/rpc.rs @@ -10,8 +10,8 @@ use std::collections::HashMap; use std::str::FromStr; use tokio_stream::StreamExt; -use anyhow::Error as AnyError; use crate::zinit::errors::ZInitError; +use anyhow::Error as AnyError; use super::api::Api; @@ -45,16 +45,45 @@ fn code_name_from(code: i32) -> &'static str { } // Map ZInit/anyhow error -> JSON-RPC ErrorObjectOwned with structured details. -fn zinit_err_to_rpc(err: AnyError, action: &str, service: Option<&str>, default_code: i32) -> ErrorObjectOwned { +fn zinit_err_to_rpc( + err: AnyError, + action: &str, + service: Option<&str>, + default_code: i32, +) -> ErrorObjectOwned { // Choose code/name/message from domain errors when possible let (code, code_name, top_msg) = match err.downcast_ref::() { - Some(ZInitError::UnknownService { name }) => (SERVICE_NOT_FOUND, "ServiceNotFound", format!("service '{}' not found", name)), - Some(ZInitError::ServiceAlreadyMonitored { name }) => (SERVICE_ALREADY_MONITORED, "ServiceAlreadyMonitored", format!("service '{}' already monitored", name)), - Some(ZInitError::ServiceIsUp { name }) => (SERVICE_IS_UP, "ServiceIsUp", format!("service '{}' is up", name)), - Some(ZInitError::ServiceIsDown { name }) => (SERVICE_IS_DOWN, "ServiceIsDown", format!("service '{}' is down", name)), - Some(ZInitError::ShuttingDown) => (SHUTTING_DOWN, "ShuttingDown", "system is shutting down".to_string()), - Some(ZInitError::InvalidStateTransition { message }) => (-32009, "InvalidStateTransition", message.clone()), - Some(ZInitError::DependencyError { message }) => (-32010, "DependencyError", message.clone()), + Some(ZInitError::UnknownService { name }) => ( + SERVICE_NOT_FOUND, + "ServiceNotFound", + format!("service '{}' not found", name), + ), + Some(ZInitError::ServiceAlreadyMonitored { name }) => ( + SERVICE_ALREADY_MONITORED, + "ServiceAlreadyMonitored", + format!("service '{}' already monitored", name), + ), + Some(ZInitError::ServiceIsUp { name }) => ( + SERVICE_IS_UP, + "ServiceIsUp", + format!("service '{}' is up", name), + ), + Some(ZInitError::ServiceIsDown { name }) => ( + SERVICE_IS_DOWN, + "ServiceIsDown", + format!("service '{}' is down", name), + ), + Some(ZInitError::ShuttingDown) => ( + SHUTTING_DOWN, + "ShuttingDown", + "system is shutting down".to_string(), + ), + Some(ZInitError::InvalidStateTransition { message }) => { + (-32009, "InvalidStateTransition", message.clone()) + } + Some(ZInitError::DependencyError { message }) => { + (-32010, "DependencyError", message.clone()) + } Some(ZInitError::ProcessError { message }) => (-32011, "ProcessError", message.clone()), None => (default_code, code_name_from(default_code), err.to_string()), }; @@ -199,30 +228,34 @@ pub trait ZinitServiceApi { #[async_trait] impl ZinitServiceApiServer for Api { async fn list(&self) -> RpcResult> { - let services = self - .zinit - .list() - .await - .map_err(|e| zinit_err_to_rpc(e, "listing services", None, ErrorCode::InternalError.code()))?; + let services = self.zinit.list().await.map_err(|e| { + zinit_err_to_rpc(e, "listing services", None, ErrorCode::InternalError.code()) + })?; let mut map: HashMap = HashMap::new(); for service in services { - let state = self - .zinit - .status(&service) - .await - .map_err(|e| zinit_err_to_rpc(e, "getting status", Some(&service), ErrorCode::InternalError.code()))?; + let state = self.zinit.status(&service).await.map_err(|e| { + zinit_err_to_rpc( + e, + "getting status", + Some(&service), + ErrorCode::InternalError.code(), + ) + })?; map.insert(service, format!("{:?}", state.state)); } Ok(map) } async fn status(&self, name: String) -> RpcResult { - let status = self - .zinit - .status(&name) - .await - .map_err(|e| zinit_err_to_rpc(e, "getting status", Some(&name), ErrorCode::InternalError.code()))?; + let status = self.zinit.status(&name).await.map_err(|e| { + zinit_err_to_rpc( + e, + "getting status", + Some(&name), + ErrorCode::InternalError.code(), + ) + })?; let result = Status { name: name.clone(), @@ -246,17 +279,25 @@ impl ZinitServiceApiServer for Api { } async fn start(&self, name: String) -> RpcResult<()> { - self.zinit - .start(name.clone()) - .await - .map_err(|e| zinit_err_to_rpc(e, "starting service", Some(&name), ErrorCode::InternalError.code())) + self.zinit.start(name.clone()).await.map_err(|e| { + zinit_err_to_rpc( + e, + "starting service", + Some(&name), + ErrorCode::InternalError.code(), + ) + }) } async fn stop(&self, name: String) -> RpcResult<()> { - self.zinit - .stop(name.clone()) - .await - .map_err(|e| zinit_err_to_rpc(e, "stopping service", Some(&name), ErrorCode::InternalError.code())) + self.zinit.stop(name.clone()).await.map_err(|e| { + zinit_err_to_rpc( + e, + "stopping service", + Some(&name), + ErrorCode::InternalError.code(), + ) + }) } async fn monitor(&self, name: String) -> RpcResult<()> { @@ -265,39 +306,53 @@ impl ZinitServiceApiServer for Api { return Err(invalid_service_name_error("monitoring service", &name)); } - let (name_str, service) = config::load(format!("{}.yaml", name)) - .map_err(|e| { - ErrorObjectOwned::owned( - CONFIG_ERROR, - format!("monitoring service: failed to load '{}.yaml'", name), - Some(json!({ - "code_name": "ConfigError", - "action": "loading service config", - "service": name, - "hint": "Verify the YAML file exists and is valid" - })), - ) - })?; + let (name_str, service) = config::load(format!("{}.yaml", name)).map_err(|e| { + ErrorObjectOwned::owned( + CONFIG_ERROR, + format!("monitoring service: failed to load '{}.yaml'", name), + Some(json!({ + "code_name": "ConfigError", + "action": "loading service config", + "service": name, + "hint": "Verify the YAML file exists and is valid" + })), + ) + })?; self.zinit .monitor(name_str.clone(), service) .await - .map_err(|e| zinit_err_to_rpc(e, "monitoring service", Some(&name_str), ErrorCode::InternalError.code())) + .map_err(|e| { + zinit_err_to_rpc( + e, + "monitoring service", + Some(&name_str), + ErrorCode::InternalError.code(), + ) + }) } async fn forget(&self, name: String) -> RpcResult<()> { - self.zinit - .forget(name.clone()) - .await - .map_err(|e| zinit_err_to_rpc(e, "forgetting service", Some(&name), ErrorCode::InternalError.code())) + self.zinit.forget(name.clone()).await.map_err(|e| { + zinit_err_to_rpc( + e, + "forgetting service", + Some(&name), + ErrorCode::InternalError.code(), + ) + }) } async fn kill(&self, name: String, signal: String) -> RpcResult<()> { if let Ok(sig) = nix::sys::signal::Signal::from_str(&signal.to_uppercase()) { - self.zinit - .kill(name.clone(), sig) - .await - .map_err(|e| zinit_err_to_rpc(e, "sending signal", Some(&name), ErrorCode::InternalError.code())) + self.zinit.kill(name.clone(), sig).await.map_err(|e| { + zinit_err_to_rpc( + e, + "sending signal", + Some(&name), + ErrorCode::InternalError.code(), + ) + }) } else { Err(invalid_signal_error(&signal)) } @@ -345,11 +400,21 @@ impl ZinitServiceApiServer for Api { })?; // Write the YAML content to the file - let mut file = fs::File::create(&file_path) - .map_err(|_| service_file_error("creating service file", &name, "failed to create service file"))?; + let mut file = fs::File::create(&file_path).map_err(|_| { + service_file_error( + "creating service file", + &name, + "failed to create service file", + ) + })?; - file.write_all(yaml_content.as_bytes()) - .map_err(|_| service_file_error("writing service file", &name, "failed to write service file"))?; + file.write_all(yaml_content.as_bytes()).map_err(|_| { + service_file_error( + "writing service file", + &name, + "failed to write service file", + ) + })?; Ok(format!("Service '{}' created successfully", name)) } @@ -381,8 +446,13 @@ impl ZinitServiceApiServer for Api { } // Delete the file - fs::remove_file(&file_path) - .map_err(|_| service_file_error("deleting service file", &name, "failed to delete service file"))?; + fs::remove_file(&file_path).map_err(|_| { + service_file_error( + "deleting service file", + &name, + "failed to delete service file", + ) + })?; Ok(format!("Service '{}' deleted successfully", name)) } @@ -414,8 +484,9 @@ impl ZinitServiceApiServer for Api { } // Read the file content - let yaml_content = fs::read_to_string(&file_path) - .map_err(|_| service_file_error("reading service file", &name, "failed to read service file"))?; + let yaml_content = fs::read_to_string(&file_path).map_err(|_| { + service_file_error("reading service file", &name, "failed to read service file") + })?; // Parse YAML to JSON let yaml_value: serde_yaml::Value = serde_yaml::from_str(&yaml_content).map_err(|_| { @@ -448,11 +519,14 @@ impl ZinitServiceApiServer for Api { } async fn stats(&self, name: String) -> RpcResult { - let stats = self - .zinit - .stats(&name) - .await - .map_err(|e| zinit_err_to_rpc(e, "collecting stats", Some(&name), ErrorCode::InternalError.code()))?; + let stats = self.zinit.stats(&name).await.map_err(|e| { + zinit_err_to_rpc( + e, + "collecting stats", + Some(&name), + ErrorCode::InternalError.code(), + ) + })?; let result = Stats { name: name.clone(), @@ -504,17 +578,21 @@ impl ZinitSystemApiServer for Api { } async fn reboot(&self) -> RpcResult<()> { - self.zinit - .reboot() - .await - .map_err(|e| zinit_err_to_rpc(e, "system reboot", None, ErrorCode::InternalError.code())) + self.zinit.reboot().await.map_err(|e| { + zinit_err_to_rpc(e, "system reboot", None, ErrorCode::InternalError.code()) + }) } async fn start_http_server(&self, address: String) -> RpcResult { // Call the method from the API implementation match crate::app::api::Api::start_http_server(self, address).await { Ok(result) => Ok(result), - Err(e) => Err(zinit_err_to_rpc(AnyError::from(e), "starting http server", None, ErrorCode::InternalError.code())), + Err(e) => Err(zinit_err_to_rpc( + AnyError::from(e), + "starting http server", + None, + ErrorCode::InternalError.code(), + )), } } @@ -522,7 +600,12 @@ impl ZinitSystemApiServer for Api { // Call the method from the API implementation match crate::app::api::Api::stop_http_server(self).await { Ok(_) => Ok(()), - Err(e) => Err(zinit_err_to_rpc(AnyError::from(e), "stopping http server", None, ErrorCode::InternalError.code())), + Err(e) => Err(zinit_err_to_rpc( + AnyError::from(e), + "stopping http server", + None, + ErrorCode::InternalError.code(), + )), } } } diff --git a/zinit-client/src/lib.rs b/zinit-client/src/lib.rs index 0d6b370..4031d03 100644 --- a/zinit-client/src/lib.rs +++ b/zinit-client/src/lib.rs @@ -88,10 +88,16 @@ impl From for ClientError { let human = match (service, action, hint) { (Some(s), Some(a), Some(h)) => { - format!("{}[{}]: {} while {} '{}'. Hint: {}. Details: {:?}", code_name, code, message, a, s, h, chain) + format!( + "{}[{}]: {} while {} '{}'. Hint: {}. Details: {:?}", + code_name, code, message, a, s, h, chain + ) } (Some(s), Some(a), None) => { - format!("{}[{}]: {} while {} '{}'. Details: {:?}", code_name, code, message, a, s, chain) + format!( + "{}[{}]: {} while {} '{}'. Details: {:?}", + code_name, code, message, a, s, chain + ) } _ => { format!("{}[{}]: {}. Details: {:?}", code_name, code, message, chain)