nvim_rs/
error.rs

1//! # Errors of nvim-rs.
2//!
3//! Nvim-rs reports very detailed errors, to facilitate debugging by logging
4//! even in rare edge cases, and to enable clients to handle errors according to
5//! their needs. Errors are boxed to not overly burden the size of the
6//! `Result`s.
7//!
8//! ### Overview
9//!
10//! Errors can originate in three ways:
11//!
12//!   1. Failure of a request to neovim is communicated by a
13//!      [`CallError`](crate::error::CallError).
14//!   2. A failure in the io loop is communicated by a
15//!      [`LoopError`](crate::error::LoopError).
16//!   3. A failure to connect to neovim when starting up via one of the
17//!      [`new_*`](crate::create) functions  is communicated by an
18//!      [`io::Error`](std::io::Error).
19//!
20//! Most errors should probably be treated as fatal, and the application should
21//! just exit.
22//!
23//!
24//! ### Special errors
25//!
26//! Use [`is_reader_error`](crate::error::LoopError::is_reader_error)
27//! to check if it might sense to try to show an error message to the neovim
28//! user (see [this example](crate::examples::scorched_earth)).
29//!
30//! Use
31//! [`CallError::is_channel_closed`](crate::error::CallError::is_channel_closed)
32//! or
33//! [`LoopError::is_channel_closed`](crate::error::LoopError::is_channel_closed)
34//! to determine if the error originates from a closed channel. This means
35//! either neovim closed the channel actively, or neovim was closed. Often, this
36//! is not seen as a real error, but the signal for the plugin to quit. Again,
37//! see the [example](crate::examples::scorched_earth).
38use std::{
39  error::Error, fmt, fmt::Display, io, io::ErrorKind, ops::RangeInclusive,
40  sync::Arc,
41};
42
43use futures::channel::oneshot;
44use rmpv::{
45  decode::Error as RmpvDecodeError, encode::Error as RmpvEncodeError, Value,
46};
47
48/// A message from neovim had an invalid format
49///
50/// This should be very basically non-existent, since it would indicate a bug in
51/// neovim.
52#[derive(Debug, PartialEq, Clone)]
53pub enum InvalidMessage {
54  /// The value read was not an array
55  NotAnArray(Value),
56  /// WrongArrayLength(should, is) means that the array should have length in
57  /// the range `should`, but has length `is`
58  WrongArrayLength(RangeInclusive<u64>, u64),
59  /// The first array element (=the message type) was not decodable into a u64
60  InvalidType(Value),
61  /// The first array element (=the message type) was decodable into a u64
62  /// larger than 2
63  UnknownMessageType(u64),
64  /// The params of a request or notification weren't an array
65  InvalidParams(Value, String),
66  /// The method name of a notification was not decodable into a String
67  InvalidNotificationName(Value),
68  /// The method name of a request was not decodable into a String
69  InvalidRequestName(u64, Value),
70  /// The msgid of a request or response was not decodable into a u64
71  InvalidMsgid(Value),
72}
73
74impl Error for InvalidMessage {}
75
76impl Display for InvalidMessage {
77  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
78    use InvalidMessage::*;
79
80    match self {
81      NotAnArray(val) => write!(fmt, "Value not an Array: '{val}'"),
82      WrongArrayLength(should, is) => write!(
83        fmt,
84        "Array should have length {:?}, has length {}",
85        should, is
86      ),
87      InvalidType(val) => {
88        write!(fmt, "Message type not decodable into u64: {val}")
89      }
90      UnknownMessageType(m) => {
91        write!(fmt, "Message type {m} is not 0, 1 or 2")
92      }
93      InvalidParams(val, s) => {
94        write!(fmt, "Params of method '{s}' not an Array: '{val}'")
95      }
96      InvalidNotificationName(val) => write!(
97        fmt,
98        "Notification name not a
99        string: '{}'",
100        val
101      ),
102      InvalidRequestName(id, val) => {
103        write!(fmt, "Request id {id}: name not valid String: '{val}'")
104      }
105      InvalidMsgid(val) => {
106        write!(fmt, "Msgid of message not decodable into u64: '{val}'")
107      }
108    }
109  }
110}
111
112/// Receiving a message from neovim failed
113#[derive(Debug)]
114pub enum DecodeError {
115  /// Reading from the internal buffer failed.
116  BufferError(RmpvDecodeError),
117  /// Reading from the stream failed. This is probably unrecoverable from, but
118  /// might also mean that neovim closed the stream and wants the plugin to
119  /// finish. See examples/quitting.rs on how this might be caught.
120  ReaderError(io::Error),
121  /// Neovim sent a message that's not valid.
122  InvalidMessage(InvalidMessage),
123}
124
125impl Error for DecodeError {
126  fn source(&self) -> Option<&(dyn Error + 'static)> {
127    match *self {
128      DecodeError::BufferError(ref e) => Some(e),
129      DecodeError::InvalidMessage(ref e) => Some(e),
130      DecodeError::ReaderError(ref e) => Some(e),
131    }
132  }
133}
134
135impl Display for DecodeError {
136  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
137    let s = match *self {
138      DecodeError::BufferError(_) => "Error while reading from buffer",
139      DecodeError::InvalidMessage(_) => "Error while decoding",
140      DecodeError::ReaderError(_) => "Error while reading from Reader",
141    };
142
143    fmt.write_str(s)
144  }
145}
146
147impl From<RmpvDecodeError> for Box<DecodeError> {
148  fn from(err: RmpvDecodeError) -> Box<DecodeError> {
149    Box::new(DecodeError::BufferError(err))
150  }
151}
152
153impl From<InvalidMessage> for Box<DecodeError> {
154  fn from(err: InvalidMessage) -> Box<DecodeError> {
155    Box::new(DecodeError::InvalidMessage(err))
156  }
157}
158
159impl From<io::Error> for Box<DecodeError> {
160  fn from(err: io::Error) -> Box<DecodeError> {
161    Box::new(DecodeError::ReaderError(err))
162  }
163}
164
165/// Sending a message to neovim failed
166#[derive(Debug)]
167pub enum EncodeError {
168  /// Encoding the message into the internal buffer has failed.
169  BufferError(RmpvEncodeError),
170  /// Writing the encoded message to the stream failed.
171  WriterError(io::Error),
172}
173
174impl Error for EncodeError {
175  fn source(&self) -> Option<&(dyn Error + 'static)> {
176    match *self {
177      EncodeError::BufferError(ref e) => Some(e),
178      EncodeError::WriterError(ref e) => Some(e),
179    }
180  }
181}
182
183impl Display for EncodeError {
184  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
185    let s = match *self {
186      Self::BufferError(_) => "Error writing to buffer",
187      Self::WriterError(_) => "Error writing to the Writer",
188    };
189
190    fmt.write_str(s)
191  }
192}
193
194impl From<RmpvEncodeError> for Box<EncodeError> {
195  fn from(err: RmpvEncodeError) -> Box<EncodeError> {
196    Box::new(EncodeError::BufferError(err))
197  }
198}
199
200impl From<io::Error> for Box<EncodeError> {
201  fn from(err: io::Error) -> Box<EncodeError> {
202    Box::new(EncodeError::WriterError(err))
203  }
204}
205
206/// A [`call`](crate::neovim::Neovim::call) to neovim failed
207///
208/// The API functions return this, as they are just
209/// proxies for [`call`](crate::neovim::Neovim::call).
210#[derive(Debug)]
211pub enum CallError {
212  /// Sending the request to neovim has failed.
213  ///
214  /// Fields:
215  ///
216  /// 0. The underlying error
217  /// 1. The name of the called method
218  SendError(EncodeError, String),
219  /// The internal channel to send the response to the right task was closed.
220  /// This really should not happen, unless someone manages to kill individual
221  /// tasks.
222  ///
223  /// Fields:
224  ///
225  /// 0. The underlying error
226  /// 1. The name of the called method
227  InternalReceiveError(oneshot::Canceled, String),
228  /// Decoding neovim's response failed.
229  ///
230  /// Fields:
231  ///
232  /// 0. The underlying error
233  /// 1. The name of the called method
234  ///
235  /// *Note*: DecodeError can't be Clone, so we Arc-wrap it
236  DecodeError(Arc<DecodeError>, String),
237  /// Neovim encountered an error while executing the reqest.
238  ///
239  /// Fields:
240  ///
241  /// 0. Neovim's error type (see `:h api`)
242  /// 1. Neovim's error message
243  NeovimError(Option<i64>, String),
244  /// The response from neovim contained a [`Value`](rmpv::Value) of the wrong
245  /// type
246  WrongValueType(Value),
247}
248
249impl Error for CallError {
250  fn source(&self) -> Option<&(dyn Error + 'static)> {
251    match *self {
252      CallError::SendError(ref e, _) => Some(e),
253      CallError::InternalReceiveError(ref e, _) => Some(e),
254      CallError::DecodeError(ref e, _) => Some(e.as_ref()),
255      CallError::NeovimError(_, _) | CallError::WrongValueType(_) => None,
256    }
257  }
258}
259
260impl CallError {
261  /// Determine if the error originated from a closed channel. This is generally
262  /// used to close a plugin from neovim's side, and so most of the time should
263  /// not be treated as a real error, but a signal to finish the program.
264  #[must_use]
265  pub fn is_channel_closed(&self) -> bool {
266    match *self {
267      CallError::SendError(EncodeError::WriterError(ref e), _)
268        if e.kind() == ErrorKind::UnexpectedEof =>
269      {
270        return true
271      }
272      CallError::DecodeError(ref err, _) => {
273        if let DecodeError::ReaderError(ref e) = err.as_ref() {
274          if e.kind() == ErrorKind::UnexpectedEof {
275            return true;
276          }
277        }
278      }
279      _ => {}
280    }
281
282    false
283  }
284}
285
286impl Display for CallError {
287  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
288    match *self {
289      Self::SendError(_, ref s) => write!(fmt, "Error sending request '{s}'"),
290      Self::InternalReceiveError(_, ref s) => {
291        write!(fmt, "Error receiving response for '{s}'")
292      }
293      Self::DecodeError(_, ref s) => {
294        write!(fmt, "Error decoding response to request '{s}'")
295      }
296      Self::NeovimError(ref i, ref s) => match i {
297        Some(i) => write!(fmt, "Error processing request: {i} - '{s}')"),
298        None => write!(
299          fmt,
300          "Error processing request, unknown error format: '{s}'"
301        ),
302      },
303      CallError::WrongValueType(ref val) => {
304        write!(fmt, "Wrong value type: '{val}'")
305      }
306    }
307  }
308}
309
310impl From<Value> for Box<CallError> {
311  fn from(val: Value) -> Box<CallError> {
312    match val {
313      Value::Array(mut arr)
314        if arr.len() == 2 && arr[0].is_i64() && arr[1].is_str() =>
315      {
316        let s = arr
317          .pop()
318          .expect("This was checked")
319          .as_str()
320          .expect("This was checked")
321          .into();
322        let i = arr.pop().expect("This was checked").as_i64();
323        Box::new(CallError::NeovimError(i, s))
324      }
325      val => Box::new(CallError::NeovimError(None, format!("{val:?}"))),
326    }
327  }
328}
329
330/// A failure in the io loop
331#[derive(Debug)]
332pub enum LoopError {
333  /// A Msgid could not be found in the request queue
334  MsgidNotFound(u64),
335  /// Decoding a message failed.
336  ///
337  /// Fields:
338  ///
339  /// 0. The underlying error
340  /// 1. The msgids of the requests we could not send the error to.
341  ///
342  /// Note: DecodeError can't be clone, so we Arc-wrap it.
343  DecodeError(Arc<DecodeError>, Option<Vec<u64>>),
344  /// Failed to send a Response (from neovim) through the sender from the
345  /// request queue
346  ///
347  /// Fields:
348  ///
349  /// 0. The msgid of the request the response was sent for
350  /// 1. The response from neovim
351  InternalSendResponseError(u64, Result<Value, Value>),
352}
353
354impl Error for LoopError {
355  fn source(&self) -> Option<&(dyn Error + 'static)> {
356    match *self {
357      LoopError::MsgidNotFound(_)
358      | LoopError::InternalSendResponseError(_, _) => None,
359      LoopError::DecodeError(ref e, _) => Some(e.as_ref()),
360    }
361  }
362}
363
364impl LoopError {
365  #[must_use]
366  pub fn is_channel_closed(&self) -> bool {
367    if let LoopError::DecodeError(ref err, _) = *self {
368      if let DecodeError::ReaderError(ref e) = err.as_ref() {
369        if e.kind() == ErrorKind::UnexpectedEof {
370          return true;
371        }
372      }
373    }
374    false
375  }
376
377  #[must_use]
378  pub fn is_reader_error(&self) -> bool {
379    if let LoopError::DecodeError(ref err, _) = *self {
380      if let DecodeError::ReaderError(_) = err.as_ref() {
381        return true;
382      }
383    }
384    false
385  }
386}
387
388impl Display for LoopError {
389  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
390    match *self {
391      Self::MsgidNotFound(i) => {
392        write!(fmt, "Could not find Msgid '{i}' in the Queue")
393      }
394      Self::DecodeError(_, ref o) => match o {
395        None => write!(fmt, "Error reading message"),
396        Some(v) => write!(
397          fmt,
398          "Error reading message, could not forward \
399           error to the following requests: '{:?}'",
400          v
401        ),
402      },
403      Self::InternalSendResponseError(i, ref res) => write!(
404        fmt,
405        "Request {i}: Could not send response, which was {:?}",
406        res
407      ),
408    }
409  }
410}
411
412impl From<(u64, Result<Value, Value>)> for Box<LoopError> {
413  fn from(res: (u64, Result<Value, Value>)) -> Box<LoopError> {
414    Box::new(LoopError::InternalSendResponseError(res.0, res.1))
415  }
416}
417
418impl From<(Arc<DecodeError>, Vec<u64>)> for Box<LoopError> {
419  fn from(v: (Arc<DecodeError>, Vec<u64>)) -> Box<LoopError> {
420    Box::new(LoopError::DecodeError(v.0, Some(v.1)))
421  }
422}
423
424impl From<u64> for Box<LoopError> {
425  fn from(i: u64) -> Box<LoopError> {
426    Box::new(LoopError::MsgidNotFound(i))
427  }
428}
429
430#[derive(Debug)]
431pub enum HandshakeError {
432  /// Sending the request to neovim has failed.
433  ///
434  /// Fields:
435  ///
436  /// 0. The underlying error
437  SendError(EncodeError),
438  /// Sending the request to neovim has failed.
439  ///
440  /// Fields:
441  ///
442  /// 0. The underlying error
443  /// 1. The data read so far
444  RecvError(io::Error, String),
445  /// Unexpected response received
446  ///
447  /// Fields:
448  ///
449  /// 0. The data read so far
450  UnexpectedResponse(String),
451  /// The launch of Neovim failed
452  ///
453  /// Fields:
454  ///
455  /// 0. The underlying error
456  LaunchError(io::Error),
457}
458
459impl From<Box<EncodeError>> for Box<HandshakeError> {
460  fn from(v: Box<EncodeError>) -> Box<HandshakeError> {
461    Box::new(HandshakeError::SendError(*v))
462  }
463}
464
465impl From<(io::Error, String)> for Box<HandshakeError> {
466  fn from(v: (io::Error, String)) -> Box<HandshakeError> {
467    Box::new(HandshakeError::RecvError(v.0, v.1))
468  }
469}
470
471impl From<io::Error> for Box<HandshakeError> {
472  fn from(v: io::Error) -> Box<HandshakeError> {
473    Box::new(HandshakeError::LaunchError(v))
474  }
475}
476
477impl Error for HandshakeError {
478  fn source(&self) -> Option<&(dyn Error + 'static)> {
479    match *self {
480      Self::SendError(ref s) => Some(s),
481      Self::RecvError(ref s, _) => Some(s),
482      Self::LaunchError(ref s) => Some(s),
483      Self::UnexpectedResponse(_) => None,
484    }
485  }
486}
487
488impl Display for HandshakeError {
489  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
490    match *self {
491      Self::SendError(ref s) => write!(fmt, "Error sending handshake '{s}'"),
492      Self::RecvError(ref s, ref output) => {
493        write!(
494          fmt,
495          "Error receiving handshake response '{s}'\n\
496             Unexpected output:\n{output}"
497        )
498      }
499      Self::LaunchError(ref s) => write!(fmt, "Error launching nvim '{s}'"),
500      Self::UnexpectedResponse(ref output) => write!(
501        fmt,
502        "Error receiving handshake response, unexpected output:\n{output}"
503      ),
504    }
505  }
506}