diff --git a/Cargo.lock b/Cargo.lock index 5e85d7660..ff5bb5006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4396,6 +4396,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "tor-error", "typetag", ] diff --git a/crates/tor-rpccmd/Cargo.toml b/crates/tor-rpccmd/Cargo.toml index 291c1b277..4407b373c 100644 --- a/crates/tor-rpccmd/Cargo.toml +++ b/crates/tor-rpccmd/Cargo.toml @@ -21,6 +21,7 @@ once_cell = "1" paste = "1" serde = { version = "1.0.103", features = ["derive"] } thiserror = "1" +tor-error = { path = "../tor-error/", version = "0.4.1" } typetag = "0.2.7" [dev-dependencies] diff --git a/crates/tor-rpccmd/src/dispatch.rs b/crates/tor-rpccmd/src/dispatch.rs index 910735bfe..53d5dc3f8 100644 --- a/crates/tor-rpccmd/src/dispatch.rs +++ b/crates/tor-rpccmd/src/dispatch.rs @@ -11,14 +11,11 @@ use futures::future::BoxFuture; use once_cell::sync::Lazy; use crate::typeid::ConstTypeId_; -use crate::{Command, Context, Object}; +use crate::{Command, Context, Object, RpcError}; /// The return type from an RPC function. #[doc(hidden)] -pub type RpcResult = Result< - Box, - Box, ->; +pub type RpcResult = Result, RpcError>; // A boxed future holding the result of an RPC command. type RpcResultFuture = BoxFuture<'static, RpcResult>; @@ -81,8 +78,7 @@ impl InvokeEntry_ { /// } /// /// rpc::rpc_invoke_fn!{ -/// // XXXX wrong error type. -/// + /// // Note that the return type of this function must be a Result, and must be /// // `Serialize + Send + 'static`. /// async fn example(obj: ExampleObject, cmd: ExampleCommand, ctx) -> Result { @@ -129,7 +125,7 @@ macro_rules! rpc_invoke_fn { $name(obj, cmd, ctx).map(|r| { let r: $crate::RpcResult = match r { Ok(v) => Ok(Box::new(v)), - Err(e) => Err(Box::new(e)) + Err(e) => Err($crate::RpcError::from(e)) }; r }).boxed() @@ -227,7 +223,7 @@ mod test { rpc_invoke_fn! { /// Hello there - async fn invoke(_obj: Animal, cmd: SayHi, _ctx) -> Result { + async fn invoke(_obj: Animal, cmd: SayHi, _ctx) -> Result { Ok(Hello{ name: format!("{:?}", cmd) }) } } diff --git a/crates/tor-rpccmd/src/err.rs b/crates/tor-rpccmd/src/err.rs new file mode 100644 index 000000000..657cf7f5c --- /dev/null +++ b/crates/tor-rpccmd/src/err.rs @@ -0,0 +1,51 @@ +//! Error-related functionality for RPC functions. + +/// An error type returned by failing RPC commands. +#[derive(serde::Serialize)] +pub struct RpcError { + /// A human-readable message. + message: String, + /// An error code inspired by json-rpc. + code: i32, + /// The ErrorKind of this error. + #[serde(serialize_with = "ser_kind")] + kind: tor_error::ErrorKind, + /// An underlying serializable object, if any, to be sent along with the + /// error. + data: Option>, +} + +impl From for RpcError +where + T: std::error::Error + tor_error::HasKind + serde::Serialize + Send + 'static, +{ + fn from(value: T) -> Self { + let message = value.to_string(); + let code = -12345; // TODO RPC: this is wrong. + let kind = value.kind(); + let boxed: Box = Box::new(value); + let data = Some(boxed); + RpcError { + message, + code, + kind, + data, + } + } +} + +fn ser_kind(kind: &tor_error::ErrorKind, s: S) -> Result { + // TODO RPC: this format is wrong and temporary. + s.serialize_str(&format!("{:?}", kind)) +} + +impl std::fmt::Debug for RpcError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RpcError") + .field("message", &self.message) + .field("code", &self.code) + .field("kind", &self.kind) + .field("data", &self.data.as_ref().map(|_| "...")) + .finish() + } +} diff --git a/crates/tor-rpccmd/src/lib.rs b/crates/tor-rpccmd/src/lib.rs index 667f49af1..001edd912 100644 --- a/crates/tor-rpccmd/src/lib.rs +++ b/crates/tor-rpccmd/src/lib.rs @@ -5,6 +5,7 @@ mod cmd; pub mod dispatch; +mod err; mod obj; #[doc(hidden)] pub mod typeid; @@ -13,6 +14,7 @@ use std::sync::Arc; pub use cmd::Command; pub use dispatch::invoke_command; +pub use err::RpcError; pub use obj::{Object, ObjectId}; #[doc(hidden)]