use std::{
error::Error,
fmt::{self, Display},
};
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: oneshot::error::RecvError,
}
impl ResponseReceiveError {
pub fn new(method: &'static str, error: oneshot::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 {}
#[cfg(test)]
mod tests {
use super::*;
#[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);
}
}