use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConnectionError {
#[error("node '{node}' is unreachable: {reason}")]
NodeUnreachable { node: String, reason: String },
#[error("authentication failed for node '{node}': invalid cookie")]
AuthenticationFailed { node: String },
#[error("already connected to node '{node}'")]
AlreadyConnected { node: String },
#[error("not connected to node '{node}'")]
NotConnected { node: String },
#[error("handshake failed with node '{node}': {reason}")]
HandshakeFailed { node: String, reason: String },
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("connection lost to node '{node}': {reason}")]
ConnectionLost { node: String, reason: String },
}
#[derive(Debug, Error)]
pub enum RpcError {
#[error("RPC to {node}:{module}:{function} timed out after {timeout_ms}ms")]
Timeout {
node: String,
module: String,
function: String,
timeout_ms: u64,
},
#[error("bad RPC to {node}:{module}:{function}: {reason}")]
BadRpc {
node: String,
module: String,
function: String,
reason: String,
},
#[error("cannot call {module}:{function} - not connected to node '{node}'")]
NodeNotConnected {
node: String,
module: String,
function: String,
},
#[error("failed to encode arguments for {module}:{function}: {reason}")]
EncodeError {
module: String,
function: String,
reason: String,
},
#[error("failed to decode response from {module}:{function}: {reason}")]
DecodeError {
module: String,
function: String,
reason: String,
},
#[error("connection error during RPC: {0}")]
Connection(#[from] ConnectionError),
}
#[derive(Debug, Error)]
pub enum ToolError {
#[error("invalid arguments for tool '{tool}': {reason}")]
InvalidArguments { tool: String, reason: String },
#[error("missing required argument '{argument}' for tool '{tool}'")]
MissingArgument { tool: String, argument: String },
#[error("tool '{tool}' requires --allow-eval flag to be enabled")]
EvalNotAllowed { tool: String },
#[error("RPC error in tool '{tool}': {source}")]
Rpc {
tool: String,
#[source]
source: RpcError,
},
#[error("connection error in tool '{tool}': {source}")]
Connection {
tool: String,
#[source]
source: ConnectionError,
},
#[error("internal error in tool '{tool}': {reason}")]
Internal { tool: String, reason: String },
}
pub type ConnectionResult<T> = Result<T, ConnectionError>;
pub type RpcResult<T> = Result<T, RpcError>;
pub type ToolResult<T> = Result<T, ToolError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connection_error_display() {
let err = ConnectionError::NodeUnreachable {
node: "foo@localhost".to_string(),
reason: "connection refused".to_string(),
};
assert!(err.to_string().contains("foo@localhost"));
assert!(err.to_string().contains("unreachable"));
}
#[test]
fn rpc_error_display() {
let err = RpcError::Timeout {
node: "bar@localhost".to_string(),
module: "erlang".to_string(),
function: "node".to_string(),
timeout_ms: 5000,
};
assert!(err.to_string().contains("bar@localhost"));
assert!(err.to_string().contains("timed out"));
}
#[test]
fn tool_error_display() {
let err = ToolError::InvalidArguments {
tool: "connect_node".to_string(),
reason: "node name cannot be empty".to_string(),
};
assert!(err.to_string().contains("connect_node"));
assert!(err.to_string().contains("invalid arguments"));
}
#[test]
fn result_type_aliases_compile() {
fn _connection_fn() -> ConnectionResult<()> {
Ok(())
}
fn _rpc_fn() -> RpcResult<String> {
Ok(String::new())
}
fn _tool_fn() -> ToolResult<Vec<u8>> {
Ok(vec![])
}
}
}