use std::{
error::Error,
fmt::{self, Display},
io,
};
use oneshot::error::RecvError;
use rmp::decode::{
DecodeStringError, MarkerReadError, NumValueReadError, ValueReadError, bytes::BytesReadError,
};
use rmpv::Value;
use tokio::sync::oneshot;
#[derive(Debug)]
pub struct NeovimError {
pub method: &'static str,
pub error_type: Option<i64>,
pub message: Value,
}
impl NeovimError {
pub fn new(method: &'static str, error: Value) -> Self {
let (error_type, message) = match error {
Value::Array(mut values) => (
values.first().and_then(Value::as_i64),
if values.len() >= 2 {
values.swap_remove(1)
} else {
Value::Nil
},
),
v => (None, v),
};
Self {
method,
error_type,
message,
}
}
}
impl Display for NeovimError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
fmt,
"Neovim returned error for request {:?}: {} (type={:?})",
self.method, self.message, self.error_type,
)
}
}
impl Error for NeovimError {}
#[derive(Debug)]
pub struct ResponseReceiveError {
pub method: &'static str,
pub error: RecvError,
}
impl ResponseReceiveError {
pub fn new(method: &'static str, error: RecvError) -> Self {
Self { method, error }
}
}
impl Display for ResponseReceiveError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
fmt,
"Could not receive response for request '{}': {}",
self.method, self.error
)
}
}
impl Error for ResponseReceiveError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.error)
}
}
#[derive(Debug)]
pub struct TypeError {
pub method: &'static str,
pub expected: &'static str,
pub actual: Value,
}
impl TypeError {
pub fn new(method: &'static str, expected: &'static str, actual: Value) -> Self {
Self {
method,
expected,
actual,
}
}
}
impl Display for TypeError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
fmt,
"Wrong type of return value for request '{}': expected {}, got {}",
self.method, self.expected, self.actual
)
}
}
impl Error for TypeError {}
#[derive(Debug)]
pub struct MessageIdNotFound {
pub msgid: u32,
}
impl MessageIdNotFound {
pub fn new(msgid: u32) -> Self {
Self { msgid }
}
}
impl Display for MessageIdNotFound {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "Unknown message ID {}", self.msgid)
}
}
impl Error for MessageIdNotFound {}
pub(crate) trait IntoIoError {
fn into_io(self) -> io::Error;
}
impl IntoIoError for BytesReadError {
#[inline]
fn into_io(self) -> io::Error {
match self {
BytesReadError::InsufficientBytes { .. } => io::ErrorKind::UnexpectedEof.into(),
_ => io::ErrorKind::InvalidData.into(),
}
}
}
impl IntoIoError for MarkerReadError<BytesReadError> {
#[inline]
fn into_io(self) -> io::Error {
self.0.into_io()
}
}
impl IntoIoError for ValueReadError<BytesReadError> {
#[inline]
fn into_io(self) -> io::Error {
match self {
ValueReadError::InvalidMarkerRead(err) | ValueReadError::InvalidDataRead(err) => {
err.into_io()
}
ValueReadError::TypeMismatch(_) => io::ErrorKind::InvalidData.into(),
}
}
}
impl IntoIoError for NumValueReadError<BytesReadError> {
#[inline]
fn into_io(self) -> io::Error {
match self {
NumValueReadError::InvalidMarkerRead(err) | NumValueReadError::InvalidDataRead(err) => {
err.into_io()
}
NumValueReadError::TypeMismatch(_) | NumValueReadError::OutOfRange => {
io::ErrorKind::InvalidData.into()
}
}
}
}
impl IntoIoError for DecodeStringError<'_, BytesReadError> {
#[inline]
fn into_io(self) -> io::Error {
match self {
DecodeStringError::InvalidMarkerRead(err) | DecodeStringError::InvalidDataRead(err) => {
err.into_io()
}
DecodeStringError::BufferSizeTooSmall(_) => io::ErrorKind::UnexpectedEof.into(),
DecodeStringError::TypeMismatch(_) | DecodeStringError::InvalidUtf8(_, _) => {
io::ErrorKind::InvalidData.into()
}
}
}
}
impl IntoIoError for rmpv::decode::Error {
#[inline]
fn into_io(self) -> io::Error {
match self {
Self::InvalidMarkerRead(err) | Self::InvalidDataRead(err) => err,
Self::DepthLimitExceeded => io::ErrorKind::InvalidData.into(),
}
}
}
pub(crate) trait IntoIoResult<T> {
fn into_io(self) -> io::Result<T>;
}
impl<T, E: IntoIoError> IntoIoResult<T> for Result<T, E> {
#[inline]
fn into_io(self) -> io::Result<T> {
self.map_err(E::into_io)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn insufficient_bytes() -> BytesReadError {
BytesReadError::InsufficientBytes {
expected: 1,
actual: 0,
position: 0,
}
}
#[test]
fn bytes_read_error_converts_to_unexpected_eof() {
assert_eq!(
insufficient_bytes().into_io().kind(),
io::ErrorKind::UnexpectedEof
);
}
#[test]
fn marker_read_error_converts_inner_error() {
let error = MarkerReadError(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
}
#[test]
fn value_read_error_converts_read_and_type_errors() {
let error = ValueReadError::InvalidMarkerRead(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error = ValueReadError::InvalidDataRead(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error: ValueReadError<BytesReadError> = ValueReadError::TypeMismatch(rmp::Marker::Null);
assert_eq!(error.into_io().kind(), io::ErrorKind::InvalidData);
}
#[test]
fn num_value_read_error_converts_read_type_and_range_errors() {
let error = NumValueReadError::InvalidMarkerRead(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error = NumValueReadError::InvalidDataRead(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error: NumValueReadError<BytesReadError> =
NumValueReadError::TypeMismatch(rmp::Marker::Null);
assert_eq!(error.into_io().kind(), io::ErrorKind::InvalidData);
let error: NumValueReadError<BytesReadError> = NumValueReadError::OutOfRange;
assert_eq!(error.into_io().kind(), io::ErrorKind::InvalidData);
}
#[test]
fn decode_string_error_converts_read_buffer_type_and_utf8_errors() {
let error = DecodeStringError::InvalidMarkerRead(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error = DecodeStringError::InvalidDataRead(insufficient_bytes()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error: DecodeStringError<'_, BytesReadError> = DecodeStringError::BufferSizeTooSmall(1);
assert_eq!(error.into_io().kind(), io::ErrorKind::UnexpectedEof);
let error: DecodeStringError<'_, BytesReadError> =
DecodeStringError::TypeMismatch(rmp::Marker::Null);
assert_eq!(error.into_io().kind(), io::ErrorKind::InvalidData);
let mut invalid = [0];
invalid[0] = 0xff;
let utf8_error = str::from_utf8(&invalid).unwrap_err();
let error: DecodeStringError<'_, BytesReadError> =
DecodeStringError::InvalidUtf8(&invalid, utf8_error);
assert_eq!(error.into_io().kind(), io::ErrorKind::InvalidData);
}
#[test]
fn rmpv_decode_error_converts_read_and_depth_errors() {
let error =
rmpv::decode::Error::InvalidMarkerRead(io::ErrorKind::UnexpectedEof.into()).into_io();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
let error =
rmpv::decode::Error::InvalidDataRead(io::ErrorKind::PermissionDenied.into()).into_io();
assert_eq!(error.kind(), io::ErrorKind::PermissionDenied);
let error = rmpv::decode::Error::DepthLimitExceeded.into_io();
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
}
#[test]
fn result_converts_to_io_result() {
let result: Result<u32, BytesReadError> = Ok(42);
assert_eq!(result.into_io().unwrap(), 42);
let result: Result<u32, BytesReadError> = Err(insufficient_bytes());
assert_eq!(
result.into_io().unwrap_err().kind(),
io::ErrorKind::UnexpectedEof
);
}
#[test]
fn neovim_error_extracts_type_and_message_from_error_array() {
let err = NeovimError::new(
"nvim_cmd",
Value::Array(vec![Value::from(1), Value::from("failed")]),
);
assert_eq!(err.method, "nvim_cmd");
assert_eq!(err.error_type, Some(1));
assert_eq!(err.message, Value::from("failed"));
}
#[test]
fn neovim_error_uses_string_value_as_message_without_error_type() {
let err = NeovimError::new("nvim_cmd", Value::from("failed"));
assert_eq!(err.method, "nvim_cmd");
assert_eq!(err.error_type, None);
assert_eq!(err.message, Value::from("failed"));
}
#[test]
fn neovim_error_uses_nil_message_when_message_is_missing() {
let err = NeovimError::new("nvim_cmd", Value::Array(vec![Value::from(1)]));
assert_eq!(err.method, "nvim_cmd");
assert_eq!(err.error_type, Some(1));
assert_eq!(err.message, Value::Nil);
}
#[tokio::test]
async fn response_receive_error_retains_method_and_source() {
let (sender, receiver) = oneshot::channel::<()>();
drop(sender);
let source = receiver.await.unwrap_err();
let err = ResponseReceiveError::new("nvim_input", source);
assert_eq!(err.method, "nvim_input");
assert!(err.source().is_some());
}
#[test]
fn type_error_retains_expected_type_and_actual_value() {
let err = TypeError::new("nvim_get_api_info", "array", Value::Nil);
assert_eq!(err.method, "nvim_get_api_info");
assert_eq!(err.expected, "array");
assert_eq!(err.actual, Value::Nil);
}
}