#[derive(serde::Serialize)]
pub struct RpcError {
message: String,
code: RpcCode,
#[serde(serialize_with = "ser_kind")]
kinds: tor_error::ErrorKind,
data: Option<Box<dyn erased_serde::Serialize + Send>>,
}
impl RpcError {
pub fn is_internal(&self) -> bool {
matches!(self.kinds, tor_error::ErrorKind::Internal)
}
}
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 = kind_to_code(value.kind());
let kinds = value.kind();
let boxed: Box<dyn erased_serde::Serialize + Send> = Box::new(value);
let data = Some(boxed);
RpcError {
message,
code,
kinds,
data,
}
}
}
impl From<crate::dispatch::InvokeError> for crate::RpcError {
fn from(_value: crate::dispatch::InvokeError) -> Self {
crate::RpcError {
message: "Tried to invoke unsupported method on object".to_string(),
code: RpcCode::MethodNotFound,
kinds: tor_error::ErrorKind::RpcMethodNotFound,
data: None,
}
}
}
impl From<crate::LookupError> for crate::RpcError {
fn from(value: crate::LookupError) -> Self {
crate::RpcError {
message: value.to_string(),
code: RpcCode::ObjectError,
kinds: tor_error::ErrorKind::RpcObjectNotFound,
data: None,
}
}
}
fn ser_kind<S: serde::Serializer>(kind: &tor_error::ErrorKind, s: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeSeq;
let mut seq = s.serialize_seq(Some(1))?;
seq.serialize_element(&format!("arti:{:?}", kind))?;
seq.end()
}
#[derive(Clone, Debug, Eq, PartialEq, serde_repr::Serialize_repr)]
#[repr(i32)]
enum RpcCode {
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
ObjectError = 1,
RequestError = 2,
}
fn kind_to_code(kind: tor_error::ErrorKind) -> RpcCode {
use tor_error::ErrorKind as EK;
match kind {
EK::RpcInvalidRequest => RpcCode::InvalidRequest,
EK::RpcMethodNotFound => RpcCode::MethodNotFound,
EK::RpcInvalidMethodParameters => RpcCode::InvalidParams,
EK::Internal | EK::BadApiUsage => RpcCode::InternalError,
EK::RpcObjectNotFound => RpcCode::ObjectError,
_ => RpcCode::RequestError, }
}
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("kinds", &self.kinds)
.field("data", &self.data.as_ref().map(|_| "..."))
.finish()
}
}
impl From<crate::SendUpdateError> for RpcError {
fn from(value: crate::SendUpdateError) -> Self {
Self {
message: value.to_string(),
code: RpcCode::RequestError,
kinds: tor_error::ErrorKind::Internal,
data: None,
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
#[derive(Debug, thiserror::Error, serde::Serialize)]
enum ExampleError {
#[error("The {} exploded because {}", what, why)]
SomethingExploded { what: String, why: String },
#[error("I'm hiding the {0} in my {1}")]
SomethingWasHidden(String, String),
#[error("The {0} was missing")]
SomethingWasMissing(String),
#[error("I don't feel up to it today")]
ProgramUnwilling,
}
impl tor_error::HasKind for ExampleError {
fn kind(&self) -> tor_error::ErrorKind {
match self {
Self::SomethingExploded { .. } => tor_error::ErrorKind::Other,
Self::SomethingWasHidden(_, _) => tor_error::ErrorKind::RpcObjectNotFound,
Self::SomethingWasMissing(_) => tor_error::ErrorKind::FeatureDisabled,
Self::ProgramUnwilling => tor_error::ErrorKind::Internal,
}
}
}
macro_rules! assert_json_eq {
($a:expr, $b:expr) => {
let json_a: serde_json::Value = serde_json::from_str($a).unwrap();
let json_b: serde_json::Value = serde_json::from_str($b).unwrap();
assert_eq!(json_a, json_b);
};
}
#[test]
fn serialize_error() {
let err = ExampleError::SomethingExploded {
what: "previous implementation".into(),
why: "worse things happen at C".into(),
};
let err = RpcError::from(err);
assert_eq!(err.code, RpcCode::RequestError);
let serialized = serde_json::to_string(&err).unwrap();
let expected_json = r#"
{
"message": "The previous implementation exploded because worse things happen at C",
"code": 2,
"kinds": ["arti:Other"],
"data": {
"SomethingExploded": {
"what": "previous implementation",
"why": "worse things happen at C"
}
}
}
"#;
assert_json_eq!(&serialized, expected_json);
let err = ExampleError::SomethingWasHidden(
"zircon-encrusted tweezers".into(),
"chrome dinette".into(),
);
let err = RpcError::from(err);
let serialized = serde_json::to_string(&err).unwrap();
let expected = r#"
{
"message": "I'm hiding the zircon-encrusted tweezers in my chrome dinette",
"code": 1,
"kinds": ["arti:RpcObjectNotFound"],
"data": {
"SomethingWasHidden": [
"zircon-encrusted tweezers",
"chrome dinette"
]
}
}
"#;
assert_json_eq!(&serialized, expected);
let err = ExampleError::SomethingWasMissing("turbo-encabulator".into());
let err = RpcError::from(err);
let serialized = serde_json::to_string(&err).unwrap();
let expected = r#"
{
"message": "The turbo-encabulator was missing",
"code": 2,
"kinds": ["arti:FeatureDisabled"],
"data": {
"SomethingWasMissing": "turbo-encabulator"
}
}
"#;
assert_json_eq!(&serialized, expected);
let err = ExampleError::ProgramUnwilling;
let err = RpcError::from(err);
let serialized = serde_json::to_string(&err).unwrap();
let expected = r#"
{
"message": "I don't feel up to it today",
"code": -32603,
"kinds": ["arti:Internal"],
"data": "ProgramUnwilling"
}
"#;
assert_json_eq!(&serialized, expected);
}
}