You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
distant/distant-protocol/src/common/error.rs

316 lines
10 KiB
Rust

use std::io;
use derive_more::Display;
use serde::{Deserialize, Serialize};
/// General purpose error type that can be sent across the wire
#[derive(Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[display(fmt = "{kind}: {description}")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Error {
/// Label describing the kind of error
pub kind: ErrorKind,
/// Description of the error itself
pub description: String,
}
impl std::error::Error for Error {}
impl Error {
/// Produces an [`io::Error`] from this error.
pub fn to_io_error(&self) -> io::Error {
io::Error::new(self.kind.into(), self.description.to_string())
}
}
impl<'a> From<&'a str> for Error {
fn from(x: &'a str) -> Self {
Self::from(x.to_string())
}
}
impl From<String> for Error {
fn from(x: String) -> Self {
Self {
kind: ErrorKind::Other,
description: x,
}
}
}
impl From<io::Error> for Error {
fn from(x: io::Error) -> Self {
Self {
kind: ErrorKind::from(x.kind()),
description: x.to_string(),
}
}
}
impl From<Error> for io::Error {
fn from(x: Error) -> Self {
Self::new(x.kind.into(), x.description)
}
}
/// All possible kinds of errors that can be returned
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ErrorKind {
/// An entity was not found, often a file
NotFound,
/// The operation lacked the necessary privileges to complete
PermissionDenied,
/// The connection was refused by the remote server
ConnectionRefused,
/// The connection was reset by the remote server
ConnectionReset,
/// The connection was aborted (terminated) by the remote server
ConnectionAborted,
/// The network operation failed because it was not connected yet
NotConnected,
/// A socket address could not be bound because the address is already in use elsewhere
AddrInUse,
/// A nonexistent interface was requested or the requested address was not local
AddrNotAvailable,
/// The operation failed because a pipe was closed
BrokenPipe,
/// An entity already exists, often a file
AlreadyExists,
/// The operation needs to block to complete, but the blocking operation was requested to not
/// occur
WouldBlock,
/// A parameter was incorrect
InvalidInput,
/// Data not valid for the operation were encountered
InvalidData,
/// The I/O operation's timeout expired, causing it to be cancelled
TimedOut,
/// An error returned when an operation could not be completed because a
/// call to `write` returned `Ok(0)`
WriteZero,
/// This operation was interrupted
Interrupted,
/// Any I/O error not part of this list
Other,
/// An error returned when an operation could not be completed because an "end of file" was
/// reached prematurely
UnexpectedEof,
/// This operation is unsupported on this platform
Unsupported,
/// An operation could not be completed, because it failed to allocate enough memory
OutOfMemory,
/// When a loop is encountered when walking a directory
Loop,
/// When a task is cancelled
TaskCancelled,
/// When a task panics
TaskPanicked,
/// Catchall for an error that has no specific type
Unknown,
}
impl From<io::ErrorKind> for ErrorKind {
fn from(kind: io::ErrorKind) -> Self {
match kind {
io::ErrorKind::NotFound => Self::NotFound,
io::ErrorKind::PermissionDenied => Self::PermissionDenied,
io::ErrorKind::ConnectionRefused => Self::ConnectionRefused,
io::ErrorKind::ConnectionReset => Self::ConnectionReset,
io::ErrorKind::ConnectionAborted => Self::ConnectionAborted,
io::ErrorKind::NotConnected => Self::NotConnected,
io::ErrorKind::AddrInUse => Self::AddrInUse,
io::ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
io::ErrorKind::BrokenPipe => Self::BrokenPipe,
io::ErrorKind::AlreadyExists => Self::AlreadyExists,
io::ErrorKind::WouldBlock => Self::WouldBlock,
io::ErrorKind::InvalidInput => Self::InvalidInput,
io::ErrorKind::InvalidData => Self::InvalidData,
io::ErrorKind::TimedOut => Self::TimedOut,
io::ErrorKind::WriteZero => Self::WriteZero,
io::ErrorKind::Interrupted => Self::Interrupted,
io::ErrorKind::Other => Self::Other,
io::ErrorKind::OutOfMemory => Self::OutOfMemory,
io::ErrorKind::UnexpectedEof => Self::UnexpectedEof,
io::ErrorKind::Unsupported => Self::Unsupported,
// This exists because io::ErrorKind is non_exhaustive
_ => Self::Unknown,
}
}
}
impl From<ErrorKind> for io::ErrorKind {
fn from(kind: ErrorKind) -> Self {
match kind {
ErrorKind::NotFound => Self::NotFound,
ErrorKind::PermissionDenied => Self::PermissionDenied,
ErrorKind::ConnectionRefused => Self::ConnectionRefused,
ErrorKind::ConnectionReset => Self::ConnectionReset,
ErrorKind::ConnectionAborted => Self::ConnectionAborted,
ErrorKind::NotConnected => Self::NotConnected,
ErrorKind::AddrInUse => Self::AddrInUse,
ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
ErrorKind::BrokenPipe => Self::BrokenPipe,
ErrorKind::AlreadyExists => Self::AlreadyExists,
ErrorKind::WouldBlock => Self::WouldBlock,
ErrorKind::InvalidInput => Self::InvalidInput,
ErrorKind::InvalidData => Self::InvalidData,
ErrorKind::TimedOut => Self::TimedOut,
ErrorKind::WriteZero => Self::WriteZero,
ErrorKind::Interrupted => Self::Interrupted,
ErrorKind::Other => Self::Other,
ErrorKind::OutOfMemory => Self::OutOfMemory,
ErrorKind::UnexpectedEof => Self::UnexpectedEof,
ErrorKind::Unsupported => Self::Unsupported,
_ => Self::Other,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod error {
use super::*;
#[test]
fn should_be_able_to_serialize_to_json() {
let error = Error {
kind: ErrorKind::AddrInUse,
description: "some description".to_string(),
};
let value = serde_json::to_value(error).unwrap();
assert_eq!(
value,
serde_json::json!({
"kind": "addr_in_use",
"description": "some description",
})
);
}
#[test]
fn should_be_able_to_deserialize_from_json() {
let value = serde_json::json!({
"kind": "addr_in_use",
"description": "some description",
});
let error: Error = serde_json::from_value(value).unwrap();
assert_eq!(
error,
Error {
kind: ErrorKind::AddrInUse,
description: "some description".to_string(),
}
);
}
#[test]
fn should_be_able_to_serialize_to_msgpack() {
let error = Error {
kind: ErrorKind::AddrInUse,
description: "some description".to_string(),
};
// NOTE: We don't actually check the output here because it's an implementation detail
// and could change as we change how serialization is done. This is merely to verify
// that we can serialize since there are times when serde fails to serialize at
// runtime.
let _ = rmp_serde::encode::to_vec_named(&error).unwrap();
}
#[test]
fn should_be_able_to_deserialize_from_msgpack() {
// NOTE: It may seem odd that we are serializing just to deserialize, but this is to
// verify that we are not corrupting or preventing issues when serializing on a
// client/server and then trying to deserialize on the other side. This has happened
// enough times with minor changes that we need tests to verify.
let buf = rmp_serde::encode::to_vec_named(&Error {
kind: ErrorKind::AddrInUse,
description: "some description".to_string(),
})
.unwrap();
let error: Error = rmp_serde::decode::from_slice(&buf).unwrap();
assert_eq!(
error,
Error {
kind: ErrorKind::AddrInUse,
description: "some description".to_string(),
}
);
}
}
mod error_kind {
use super::*;
#[test]
fn should_be_able_to_serialize_to_json() {
let kind = ErrorKind::AddrInUse;
let value = serde_json::to_value(kind).unwrap();
assert_eq!(value, serde_json::json!("addr_in_use"));
}
#[test]
fn should_be_able_to_deserialize_from_json() {
let value = serde_json::json!("addr_in_use");
let kind: ErrorKind = serde_json::from_value(value).unwrap();
assert_eq!(kind, ErrorKind::AddrInUse);
}
#[test]
fn should_be_able_to_serialize_to_msgpack() {
let kind = ErrorKind::AddrInUse;
// NOTE: We don't actually check the output here because it's an implementation detail
// and could change as we change how serialization is done. This is merely to verify
// that we can serialize since there are times when serde fails to serialize at
// runtime.
let _ = rmp_serde::encode::to_vec_named(&kind).unwrap();
}
#[test]
fn should_be_able_to_deserialize_from_msgpack() {
// NOTE: It may seem odd that we are serializing just to deserialize, but this is to
// verify that we are not corrupting or causing issues when serializing on a
// client/server and then trying to deserialize on the other side. This has happened
// enough times with minor changes that we need tests to verify.
let buf = rmp_serde::encode::to_vec_named(&ErrorKind::AddrInUse).unwrap();
let kind: ErrorKind = rmp_serde::decode::from_slice(&buf).unwrap();
assert_eq!(kind, ErrorKind::AddrInUse);
}
}
}