Lines
82.54 %
Functions
33.33 %
Branches
100 %
//! Error-related functionality for RPC functions.
use std::collections::HashMap;
/// Alias for a type-erased value used in an error's `data` field
type ErrorDatum = Box<dyn erased_serde::Serialize + Send + 'static>;
/// An error type returned by failing RPC methods.
#[derive(serde::Serialize)]
pub struct RpcError {
/// A human-readable message.
message: String,
/// An error code inspired by json-rpc.
#[serde(serialize_with = "ser_code")]
code: RpcErrorKind,
/// The ErrorKind(s) of this error.
#[serde(serialize_with = "ser_kind")]
kinds: AnyErrorKind,
/// Map from namespaced keyword to related data.
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<HashMap<String, ErrorDatum>>,
}
impl RpcError {
/// Construct a new `RpcError` with the provided message and error code.
pub fn new(message: String, code: RpcErrorKind) -> Self {
Self {
message,
code,
kinds: AnyErrorKind::Rpc(code),
data: None,
/// Change the declared kind of this error to `kind`.
pub fn set_kind(&mut self, kind: tor_error::ErrorKind) {
self.kinds = AnyErrorKind::Tor(kind);
/// Replace the `data` field named `keyword`, if any, with the object `datum`.
///
/// Note that to conform with the spec, keyword must be a C identifier prefixed with a
/// namespace, as in `rpc:missing_features`
pub fn set_datum<D>(
&mut self,
keyword: String,
datum: D,
) -> Result<(), crate::InvalidRpcIdentifier>
where
D: serde::Serialize + Send + 'static,
{
crate::is_valid_rpc_identifier(None, &keyword)?;
self.data
.get_or_insert_with(HashMap::new)
.insert(keyword, Box::new(datum) as _);
Ok(())
/// Return true if this is an internal error.
pub fn is_internal(&self) -> bool {
matches!(
self.kinds,
AnyErrorKind::Tor(tor_error::ErrorKind::Internal)
| AnyErrorKind::Rpc(RpcErrorKind::InternalError)
)
impl<T> From<T> for RpcError
T: std::error::Error + tor_error::HasKind + Send + 'static,
fn from(value: T) -> RpcError {
use tor_error::ErrorReport as _;
let message = value.report().to_string();
let code = kind_to_code(value.kind());
let kinds = AnyErrorKind::Tor(value.kind());
RpcError {
kinds,
/// Helper: Serialize an AnyErrorKind in RpcError.
fn ser_kind<S: serde::Serializer>(kind: &AnyErrorKind, s: S) -> Result<S::Ok, S::Error> {
// Our spec says that `kinds` is a list. Any tor_error::ErrorKind is prefixed with `arti:`,
// and any RpcErrorKind is prefixed with `rpc:`
use serde::ser::SerializeSeq;
let mut seq = s.serialize_seq(None)?;
match kind {
AnyErrorKind::Tor(kind) => seq.serialize_element(&format!("arti:{:?}", kind))?,
AnyErrorKind::Rpc(kind) => seq.serialize_element(&format!("rpc:{:?}", kind))?,
seq.end()
/// Helper: Serialize an RpcErrorKind as a numeric code.
fn ser_code<S: serde::Serializer>(kind: &RpcErrorKind, s: S) -> Result<S::Ok, S::Error> {
s.serialize_i32(*kind as i32)
/// An ErrorKind as held by an `RpcError`
#[derive(Clone, Copy, Debug)]
enum AnyErrorKind {
/// An ErrorKind representing a non-RPC problem.
Tor(tor_error::ErrorKind),
/// An ErrorKind originating within the RPC system.
#[allow(unused)]
Rpc(RpcErrorKind),
/// Error kinds for RPC errors.
/// Unlike `tor_error::ErrorKind`,
/// these codes do not represent a problem in an Arti function per se:
/// they are only visible to the RPC system, and should only be reported there.
/// For backward compatibility with json-rpc,
/// each of these codes has a unique numeric ID.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
pub enum RpcErrorKind {
/// "The JSON sent is not a valid Request object."
InvalidRequest = -32600,
/// "The method does not exist."
NoSuchMethod = -32601,
/// "Invalid method parameter(s)."
InvalidMethodParameters = -32602,
/// "The server suffered some kind of internal problem"
InternalError = -32603,
/// "Some requested object was not valid"
ObjectNotFound = 1,
/// "Some other error occurred"
RequestError = 2,
/// This method exists, but wasn't implemented on this object.
MethodNotImpl = 3,
/// This request was cancelled before it could finish.
RequestCancelled = 4,
/// This request listed a required feature that doesn't exist.
FeatureNotPresent = 5,
/// Helper: Return an error code (for backward compat with json-rpc) for an
/// ErrorKind.
/// These are not especially helpful and nobody should really use them.
fn kind_to_code(kind: tor_error::ErrorKind) -> RpcErrorKind {
use tor_error::ErrorKind as EK;
use RpcErrorKind as RC;
EK::Internal | EK::BadApiUsage => RC::InternalError,
_ => RC::RequestError, // (This is our catch-all "request error.")
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)
.finish()
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![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)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
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::RemoteHostNotFound,
Self::SomethingWasMissing(_) => tor_error::ErrorKind::FeatureDisabled,
Self::ProgramUnwilling => tor_error::ErrorKind::Internal,
/// Assert that two json strings deserialize to equivalent objects.
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, RpcErrorKind::RequestError);
let serialized = serde_json::to_string(&err).unwrap();
let expected_json = r#"
"message": "error: The previous implementation exploded because worse things happen at C",
"code": 2,
"kinds": ["arti:Other"]
"#;
assert_json_eq!(&serialized, expected_json);
let err = ExampleError::SomethingWasHidden(
"zircon-encrusted tweezers".into(),
"chrome dinette".into(),
);
let expected = r#"
"message": "error: I'm hiding the zircon-encrusted tweezers in my chrome dinette",
"kinds": ["arti:RemoteHostNotFound"]
assert_json_eq!(&serialized, expected);
let err = ExampleError::SomethingWasMissing("turbo-encabulator".into());
"message": "error: The turbo-encabulator was missing",
"kinds": ["arti:FeatureDisabled"]
let err = ExampleError::ProgramUnwilling;
"message": "error: I don't feel up to it today",
"code": -32603,
"kinds": ["arti:Internal"]
fn create_error() {
let mut e = RpcError::new("Example error".to_string(), RpcErrorKind::RequestError);
e.set_kind(tor_error::ErrorKind::CacheCorrupted);
e.set_datum("rpc:example".to_string(), "Hello world".to_string())
.unwrap();
let serialized = serde_json::to_string(&e).unwrap();
"message": "Example error",
"kinds": ["arti:CacheCorrupted"],
"data": {
"rpc:example": "Hello world"