use std::{
fmt::{Display, Formatter, Result as FmtResult},
io::{self, ErrorKind},
result,
};
#[cfg(feature = "serde")]
use rmp_serde::{decode::Error as RmpSerdeDecodeError, encode::Error as RmpSerdeEncodeError};
use rmpv::{Value, decode::Error as RmpvDecodeError, encode::Error as RmpvEncodeError};
use thiserror::Error;
use tokio::task::JoinError;
#[derive(Debug, Error)]
pub enum ProtocolError {
#[error("Invalid message format")]
InvalidMessageFormat,
#[error("Empty message array")]
EmptyMessageArray,
#[error("Invalid message type: {0}")]
InvalidMessageType(u64),
#[error("Invalid {kind} message length")]
InvalidMessageLength {
kind: &'static str,
},
#[error("Invalid {kind} {field}")]
InvalidMessageField {
kind: &'static str,
field: &'static str,
},
#[error("Depth limit exceeded")]
DepthLimitExceeded,
#[error("No listener configured")]
ListenerNotConfigured,
#[error("Listener has no SocketAddr")]
MissingSocketAddr,
#[error("Expected exactly one parameter")]
ExpectedSingleParameter,
#[error("Resource already taken: {resource}")]
ResourceAlreadyTaken {
resource: &'static str,
},
#[error("Task '{task}' failed: {source}")]
TaskFailed {
task: &'static str,
#[source]
source: JoinError,
},
#[error("Unexpected response id: {id}")]
UnexpectedResponse {
id: u32,
},
#[error("Malformed message: {0}")]
MalformedMessage(String),
}
impl From<&str> for ProtocolError {
fn from(message: &str) -> Self {
Self::MalformedMessage(message.to_string())
}
}
impl From<String> for ProtocolError {
fn from(message: String) -> Self {
Self::MalformedMessage(message)
}
}
#[derive(Error, Debug)]
pub enum RpcError {
#[error("I/O error: {0}")]
Io(io::Error),
#[error("Connection failed")]
Connect {
#[source]
source: io::Error,
},
#[error("Serialization error: {0}")]
Serialization(#[from] RmpvEncodeError),
#[error("Deserialization error: {0}")]
Deserialization(#[from] RmpvDecodeError),
#[cfg(feature = "serde")]
#[error("Request serialization error: {0}")]
RequestSerialization(#[from] RmpSerdeEncodeError),
#[cfg(feature = "serde")]
#[error("Response deserialization error: {0}")]
ResponseDeserialization(#[from] RmpSerdeDecodeError),
#[error(transparent)]
Protocol(#[from] ProtocolError),
#[error("Service error: {0}")]
Service(ServiceError),
#[error("Connection disconnected")]
Disconnect {
#[source]
source: Option<io::Error>,
},
}
#[derive(Error, Debug)]
pub struct ServiceError {
pub name: String,
pub value: Value,
}
impl ServiceError {
pub fn method_not_found(method: &str) -> Self {
Self {
name: "MethodNotFound".to_string(),
value: Value::String(format!("Method '{}' not found", method).into()),
}
}
}
impl Display for ServiceError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "Service error {}: {:?}", self.name, self.value)
}
}
impl From<ServiceError> for Value {
fn from(error: ServiceError) -> Self {
Self::Map(vec![
(Self::String("name".into()), Self::String(error.name.into())),
(Self::String("value".into()), error.value),
])
}
}
impl TryFrom<Value> for ServiceError {
type Error = Value;
fn try_from(value: Value) -> result::Result<Self, Self::Error> {
if let Value::Map(map) = &value {
let mut name = None;
let mut service_value = None;
for (key, entry) in map {
match key.as_str() {
Some("name") => {
name = entry.as_str().map(ToOwned::to_owned);
}
Some("value") => {
service_value = Some(entry.clone());
}
_ => {}
}
}
if let (Some(name), Some(service_value)) = (name, service_value) {
return Ok(Self {
name,
value: service_value,
});
}
}
Err(value)
}
}
impl RpcError {
pub(crate) fn task_failed(task: &'static str, source: JoinError) -> Self {
Self::Protocol(ProtocolError::TaskFailed { task, source })
}
pub(crate) fn resource_already_taken(resource: &'static str) -> Self {
Self::Protocol(ProtocolError::ResourceAlreadyTaken { resource })
}
pub(crate) fn from_remote_error_value(value: Value) -> Self {
match ServiceError::try_from(value) {
Ok(service_error) => Self::Service(service_error),
Err(Value::Map(map)) => Self::Service(ServiceError {
name: "UnknownError".to_string(),
value: Value::Map(map),
}),
Err(original_value) => Self::Service(ServiceError {
name: "RemoteError".to_string(),
value: original_value,
}),
}
}
}
impl From<io::Error> for RpcError {
fn from(error: io::Error) -> Self {
match error.kind() {
ErrorKind::UnexpectedEof
| ErrorKind::BrokenPipe
| ErrorKind::ConnectionAborted
| ErrorKind::ConnectionReset
| ErrorKind::NotConnected => Self::Disconnect {
source: Some(error),
},
_ => Self::Io(error),
}
}
}
pub type Result<T> = result::Result<T, RpcError>;
#[cfg(test)]
mod tests {
use futures::future::pending;
use super::*;
#[tokio::test]
async fn test_task_failed_wraps_join_error() {
let handle = tokio::spawn(async {
pending::<()>().await;
});
handle.abort();
let join_error = handle.await.unwrap_err();
let error = RpcError::task_failed("demo task", join_error);
match error {
RpcError::Protocol(ProtocolError::TaskFailed { task, source }) => {
assert_eq!(task, "demo task");
assert!(source.is_cancelled());
}
other => panic!("expected task failure, got {other:?}"),
}
}
#[test]
fn test_resource_already_taken_uses_protocol_error() {
let error = RpcError::resource_already_taken("message receiver");
match error {
RpcError::Protocol(ProtocolError::ResourceAlreadyTaken { resource }) => {
assert_eq!(resource, "message receiver");
}
other => panic!("expected resource-taken error, got {other:?}"),
}
}
#[test]
fn test_method_not_found_helper_uses_standard_shape() {
let error = ServiceError::method_not_found("missing");
assert_eq!(error.name, "MethodNotFound");
assert_eq!(
error.value,
Value::String("Method 'missing' not found".into())
);
}
#[test]
fn test_service_error_round_trip() {
let error = ServiceError {
name: "MethodNotFound".to_string(),
value: Value::from("missing"),
};
let encoded = Value::from(error);
let decoded = ServiceError::try_from(encoded).unwrap();
assert_eq!(decoded.name, "MethodNotFound");
assert_eq!(decoded.value, Value::from("missing"));
}
#[test]
fn test_service_error_try_from_requires_name_and_value() {
let missing_name = Value::Map(vec![(Value::from("value"), Value::from("missing"))]);
assert!(ServiceError::try_from(missing_name).is_err());
let missing_value = Value::Map(vec![(Value::from("name"), Value::from("SomeError"))]);
assert!(ServiceError::try_from(missing_value).is_err());
}
#[test]
fn test_from_remote_error_value_preserves_service_errors() {
let value = Value::Map(vec![
(Value::from("name"), Value::from("SomeError")),
(Value::from("value"), Value::from("payload")),
]);
let error = RpcError::from_remote_error_value(value);
match error {
RpcError::Service(service_error) => {
assert_eq!(service_error.name, "SomeError");
assert_eq!(service_error.value, Value::from("payload"));
}
other => panic!("expected service error, got {other:?}"),
}
}
#[test]
fn test_from_remote_error_value_uses_fallback_names() {
let malformed_map = Value::Map(vec![(Value::from("value"), Value::from("payload"))]);
let error = RpcError::from_remote_error_value(malformed_map);
match error {
RpcError::Service(service_error) => {
assert_eq!(service_error.name, "UnknownError");
assert_eq!(
service_error.value,
Value::Map(vec![(Value::from("value"), Value::from("payload"),)])
);
}
other => panic!("expected service error, got {other:?}"),
}
let scalar_error = RpcError::from_remote_error_value(Value::from("boom"));
match scalar_error {
RpcError::Service(service_error) => {
assert_eq!(service_error.name, "RemoteError");
assert_eq!(service_error.value, Value::from("boom"));
}
other => panic!("expected service error, got {other:?}"),
}
}
}