rpc: Add an RpcError type.

This could have been a trait instead, but I don't know whether
that's smart or not.  There is a lot of opportunity for refactoring
here.
This commit is contained in:
Nick Mathewson 2023-04-04 11:05:35 -04:00
parent dec23c1cf6
commit dce9c530ec
5 changed files with 60 additions and 9 deletions

1
Cargo.lock generated
View File

@ -4396,6 +4396,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"tor-error",
"typetag",
]

View File

@ -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]

View File

@ -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<dyn erased_serde::Serialize + Send + 'static>,
Box<dyn erased_serde::Serialize + Send + 'static>,
>;
pub type RpcResult = Result<Box<dyn erased_serde::Serialize + Send + 'static>, 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<ExampleResult, String> {
@ -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<Hello, String> {
async fn invoke(_obj: Animal, cmd: SayHi, _ctx) -> Result<Hello, crate::RpcError> {
Ok(Hello{ name: format!("{:?}", cmd) })
}
}

View File

@ -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<Box<dyn erased_serde::Serialize + Send>>,
}
impl<T> From<T> 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<dyn erased_serde::Serialize + Send> = Box::new(value);
let data = Some(boxed);
RpcError {
message,
code,
kind,
data,
}
}
}
fn ser_kind<S: serde::Serializer>(kind: &tor_error::ErrorKind, s: S) -> Result<S::Ok, S::Error> {
// 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()
}
}

View File

@ -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)]