Skip to main content

mrpc/
error.rs

1use std::{
2    fmt::{Display, Formatter, Result as FmtResult},
3    io::{self, ErrorKind},
4    result,
5};
6
7#[cfg(feature = "serde")]
8use rmp_serde::{decode::Error as RmpSerdeDecodeError, encode::Error as RmpSerdeEncodeError};
9use rmpv::{Value, decode::Error as RmpvDecodeError, encode::Error as RmpvEncodeError};
10use thiserror::Error;
11use tokio::task::JoinError;
12
13/// Errors indicating a violation of the MessagePack-RPC protocol or message framing.
14#[derive(Debug, Error)]
15pub enum ProtocolError {
16    /// Received a value that was not a top-level MessagePack-RPC message array.
17    #[error("Invalid message format")]
18    InvalidMessageFormat,
19
20    /// Received an empty top-level MessagePack-RPC message array.
21    #[error("Empty message array")]
22    EmptyMessageArray,
23
24    /// Received a message with an invalid type tag.
25    #[error("Invalid message type: {0}")]
26    InvalidMessageType(u64),
27
28    /// Received a message body with an unexpected number of fields.
29    #[error("Invalid {kind} message length")]
30    InvalidMessageLength {
31        /// The message kind whose field count was invalid.
32        kind: &'static str,
33    },
34
35    /// Received a message field that did not have the expected shape or type.
36    #[error("Invalid {kind} {field}")]
37    InvalidMessageField {
38        /// The message kind or decode context.
39        kind: &'static str,
40        /// The field whose value was invalid.
41        field: &'static str,
42    },
43
44    /// A full MessagePack value exceeded the configured nesting limit.
45    #[error("Depth limit exceeded")]
46    DepthLimitExceeded,
47
48    /// A caller requested a bound socket address before any listener was configured.
49    #[error("No listener configured")]
50    ListenerNotConfigured,
51
52    /// A configured listener does not expose a bound `SocketAddr`.
53    #[error("Listener has no SocketAddr")]
54    MissingSocketAddr,
55
56    /// A MessagePack-RPC single-parameter helper received the wrong arity.
57    #[error("Expected exactly one parameter")]
58    ExpectedSingleParameter,
59
60    /// A single-consumer resource was taken more than once.
61    #[error("Resource already taken: {resource}")]
62    ResourceAlreadyTaken {
63        /// The resource that was already taken.
64        resource: &'static str,
65    },
66
67    /// A background task failed before completing normally.
68    #[error("Task '{task}' failed: {source}")]
69    TaskFailed {
70        /// The task that failed.
71        task: &'static str,
72        /// The underlying join failure.
73        #[source]
74        source: JoinError,
75    },
76
77    /// Received a response for a request id that has no pending waiter.
78    #[error("Unexpected response id: {id}")]
79    UnexpectedResponse {
80        /// The request id.
81        id: u32,
82    },
83
84    /// Received a message that does not match the expected structure.
85    #[error("Malformed message: {0}")]
86    MalformedMessage(String),
87}
88
89impl From<&str> for ProtocolError {
90    fn from(message: &str) -> Self {
91        Self::MalformedMessage(message.to_string())
92    }
93}
94
95impl From<String> for ProtocolError {
96    fn from(message: String) -> Self {
97        Self::MalformedMessage(message)
98    }
99}
100
101/// Errors that can occur during RPC operations.
102#[derive(Error, Debug)]
103pub enum RpcError {
104    /// Error occurred during I/O operations.
105    #[error("I/O error: {0}")]
106    Io(io::Error),
107
108    /// Error occurred while trying to establish a connection.
109    #[error("Connection failed")]
110    Connect {
111        /// Underlying I/O error.
112        #[source]
113        source: io::Error,
114    },
115
116    /// Error occurred during MessagePack serialization.
117    #[error("Serialization error: {0}")]
118    Serialization(#[from] RmpvEncodeError),
119
120    /// Error occurred during MessagePack deserialization.
121    #[error("Deserialization error: {0}")]
122    Deserialization(#[from] RmpvDecodeError),
123
124    /// Failed to serialize request parameters.
125    #[cfg(feature = "serde")]
126    #[error("Request serialization error: {0}")]
127    RequestSerialization(#[from] RmpSerdeEncodeError),
128
129    /// Failed to deserialize a response body.
130    #[cfg(feature = "serde")]
131    #[error("Response deserialization error: {0}")]
132    ResponseDeserialization(#[from] RmpSerdeDecodeError),
133
134    /// Error related to the MessagePack-RPC protocol.
135    #[error(transparent)]
136    Protocol(#[from] ProtocolError),
137
138    /// Error returned by the RPC service implementation.
139    #[error("Service error: {0}")]
140    Service(ServiceError),
141
142    /// The connection was closed.
143    #[error("Connection disconnected")]
144    Disconnect {
145        /// Underlying I/O error, when available.
146        #[source]
147        source: Option<io::Error>,
148    },
149}
150
151/// An error that occurred during the execution of an RPC service method.
152///
153/// It consists of a name, which identifies the type of error, and a value, which can contain
154/// additional error details. This error type is used to convey service-specific errors back to the
155/// client. When sent over the RPC protocol, this error will be serialized into a map with "name"
156/// and "value" keys.
157#[derive(Error, Debug)]
158pub struct ServiceError {
159    /// The error type name.
160    pub name: String,
161    /// Additional error data.
162    pub value: Value,
163}
164
165impl ServiceError {
166    /// Creates a standard service error for an unknown RPC method.
167    pub fn method_not_found(method: &str) -> Self {
168        Self {
169            name: "MethodNotFound".to_string(),
170            value: Value::String(format!("Method '{}' not found", method).into()),
171        }
172    }
173}
174
175impl Display for ServiceError {
176    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
177        write!(f, "Service error {}: {:?}", self.name, self.value)
178    }
179}
180
181impl From<ServiceError> for Value {
182    fn from(error: ServiceError) -> Self {
183        Self::Map(vec![
184            (Self::String("name".into()), Self::String(error.name.into())),
185            (Self::String("value".into()), error.value),
186        ])
187    }
188}
189
190impl TryFrom<Value> for ServiceError {
191    type Error = Value;
192
193    fn try_from(value: Value) -> result::Result<Self, Self::Error> {
194        if let Value::Map(map) = &value {
195            let mut name = None;
196            let mut service_value = None;
197
198            for (key, entry) in map {
199                match key.as_str() {
200                    Some("name") => {
201                        name = entry.as_str().map(ToOwned::to_owned);
202                    }
203                    Some("value") => {
204                        service_value = Some(entry.clone());
205                    }
206                    _ => {}
207                }
208            }
209
210            if let (Some(name), Some(service_value)) = (name, service_value) {
211                return Ok(Self {
212                    name,
213                    value: service_value,
214                });
215            }
216        }
217        Err(value)
218    }
219}
220
221impl RpcError {
222    /// Wraps a failed task join as a protocol error with task context.
223    pub(crate) fn task_failed(task: &'static str, source: JoinError) -> Self {
224        Self::Protocol(ProtocolError::TaskFailed { task, source })
225    }
226
227    /// Reports that a single-consumer resource was taken more than once.
228    pub(crate) fn resource_already_taken(resource: &'static str) -> Self {
229        Self::Protocol(ProtocolError::ResourceAlreadyTaken { resource })
230    }
231
232    /// Builds a service-facing RPC error from a remote error payload.
233    ///
234    /// Properly encoded service errors preserve their original name and value.
235    /// Malformed remote errors are wrapped into fallback names so callers still
236    /// receive the remote payload through the service-error path.
237    pub(crate) fn from_remote_error_value(value: Value) -> Self {
238        match ServiceError::try_from(value) {
239            Ok(service_error) => Self::Service(service_error),
240            Err(Value::Map(map)) => Self::Service(ServiceError {
241                name: "UnknownError".to_string(),
242                value: Value::Map(map),
243            }),
244            Err(original_value) => Self::Service(ServiceError {
245                name: "RemoteError".to_string(),
246                value: original_value,
247            }),
248        }
249    }
250}
251
252impl From<io::Error> for RpcError {
253    fn from(error: io::Error) -> Self {
254        match error.kind() {
255            ErrorKind::UnexpectedEof
256            | ErrorKind::BrokenPipe
257            | ErrorKind::ConnectionAborted
258            | ErrorKind::ConnectionReset
259            | ErrorKind::NotConnected => Self::Disconnect {
260                source: Some(error),
261            },
262            _ => Self::Io(error),
263        }
264    }
265}
266
267/// A type alias for `Result` with [`RpcError`] as the error type.
268pub type Result<T> = result::Result<T, RpcError>;
269
270#[cfg(test)]
271mod tests {
272    use futures::future::pending;
273
274    use super::*;
275
276    #[tokio::test]
277    async fn test_task_failed_wraps_join_error() {
278        let handle = tokio::spawn(async {
279            pending::<()>().await;
280        });
281        handle.abort();
282        let join_error = handle.await.unwrap_err();
283
284        let error = RpcError::task_failed("demo task", join_error);
285
286        match error {
287            RpcError::Protocol(ProtocolError::TaskFailed { task, source }) => {
288                assert_eq!(task, "demo task");
289                assert!(source.is_cancelled());
290            }
291            other => panic!("expected task failure, got {other:?}"),
292        }
293    }
294
295    #[test]
296    fn test_resource_already_taken_uses_protocol_error() {
297        let error = RpcError::resource_already_taken("message receiver");
298
299        match error {
300            RpcError::Protocol(ProtocolError::ResourceAlreadyTaken { resource }) => {
301                assert_eq!(resource, "message receiver");
302            }
303            other => panic!("expected resource-taken error, got {other:?}"),
304        }
305    }
306
307    #[test]
308    fn test_method_not_found_helper_uses_standard_shape() {
309        let error = ServiceError::method_not_found("missing");
310
311        assert_eq!(error.name, "MethodNotFound");
312        assert_eq!(
313            error.value,
314            Value::String("Method 'missing' not found".into())
315        );
316    }
317
318    #[test]
319    fn test_service_error_round_trip() {
320        let error = ServiceError {
321            name: "MethodNotFound".to_string(),
322            value: Value::from("missing"),
323        };
324
325        let encoded = Value::from(error);
326        let decoded = ServiceError::try_from(encoded).unwrap();
327
328        assert_eq!(decoded.name, "MethodNotFound");
329        assert_eq!(decoded.value, Value::from("missing"));
330    }
331
332    #[test]
333    fn test_service_error_try_from_requires_name_and_value() {
334        let missing_name = Value::Map(vec![(Value::from("value"), Value::from("missing"))]);
335        assert!(ServiceError::try_from(missing_name).is_err());
336
337        let missing_value = Value::Map(vec![(Value::from("name"), Value::from("SomeError"))]);
338        assert!(ServiceError::try_from(missing_value).is_err());
339    }
340
341    #[test]
342    fn test_from_remote_error_value_preserves_service_errors() {
343        let value = Value::Map(vec![
344            (Value::from("name"), Value::from("SomeError")),
345            (Value::from("value"), Value::from("payload")),
346        ]);
347
348        let error = RpcError::from_remote_error_value(value);
349
350        match error {
351            RpcError::Service(service_error) => {
352                assert_eq!(service_error.name, "SomeError");
353                assert_eq!(service_error.value, Value::from("payload"));
354            }
355            other => panic!("expected service error, got {other:?}"),
356        }
357    }
358
359    #[test]
360    fn test_from_remote_error_value_uses_fallback_names() {
361        let malformed_map = Value::Map(vec![(Value::from("value"), Value::from("payload"))]);
362        let error = RpcError::from_remote_error_value(malformed_map);
363
364        match error {
365            RpcError::Service(service_error) => {
366                assert_eq!(service_error.name, "UnknownError");
367                assert_eq!(
368                    service_error.value,
369                    Value::Map(vec![(Value::from("value"), Value::from("payload"),)])
370                );
371            }
372            other => panic!("expected service error, got {other:?}"),
373        }
374
375        let scalar_error = RpcError::from_remote_error_value(Value::from("boom"));
376        match scalar_error {
377            RpcError::Service(service_error) => {
378                assert_eq!(service_error.name, "RemoteError");
379                assert_eq!(service_error.value, Value::from("boom"));
380            }
381            other => panic!("expected service error, got {other:?}"),
382        }
383    }
384}