1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
//! # Errors of nvim-rs.
//!
//! Nvim-rs reports very detailed errors, to facilitate debugging by logging
//! even in rare edge cases, and to enable clients to handle errors according to
//! their needs. Errors are boxed to not overly burden the size of the
//! `Result`s.
//!
//! ### Overview
//!
//! Errors can originate in three ways:
//!
//!   1. Failure of a request to neovim is communicated by a
//!      [`CallError`](crate::error::CallError).
//!   2. A failure in the io loop is communicated by a
//!      [`LoopError`](crate::error::LoopError).
//!   3. A failure to connect to neovim when starting up via one of the
//!      [`new_*`](crate::create) functions  is communicated by an
//!      [`io::Error`](std::io::Error).
//!
//! Most errors should probably be treated as fatal, and the application should
//! just exit.
//!
//!
//! ### Special errors
//!
//! Use [`is_reader_error`](crate::error::LoopError::is_reader_error)
//! to check if it might sense to try to show an error message to the neovim
//! user (see [this example](crate::examples::scorched_earth)).
//!
//! Use
//! [`CallError::is_channel_closed`](crate::error::CallError::is_channel_closed)
//! or
//! [`LoopError::is_channel_closed`](crate::error::LoopError::is_channel_closed)
//! to determine if the error originates from a closed channel. This means
//! either neovim closed the channel actively, or neovim was closed. Often, this
//! is not seen as a real error, but the signal for the plugin to quit. Again,
//! see the [example](crate::examples::scorched_earth).
use std::{
  error::Error, fmt, fmt::Display, io, io::ErrorKind, ops::RangeInclusive,
  sync::Arc,
};

use futures::channel::oneshot;
use rmpv::{
  decode::Error as RmpvDecodeError, encode::Error as RmpvEncodeError, Value,
};

/// A message from neovim had an invalid format
///
/// This should be very basically non-existent, since it would indicate a bug in
/// neovim.
#[derive(Debug, PartialEq, Clone)]
pub enum InvalidMessage {
  /// The value read was not an array
  NotAnArray(Value),
  /// WrongArrayLength(should, is) means that the array should have length in
  /// the range `should`, but has length `is`
  WrongArrayLength(RangeInclusive<u64>, u64),
  /// The first array element (=the message type) was not decodable into a u64
  InvalidType(Value),
  /// The first array element (=the message type) was decodable into a u64
  /// larger than 2
  UnknownMessageType(u64),
  /// The params of a request or notification weren't an array
  InvalidParams(Value, String),
  /// The method name of a notification was not decodable into a String
  InvalidNotificationName(Value),
  /// The method name of a request was not decodable into a String
  InvalidRequestName(u64, Value),
  /// The msgid of a request or response was not decodable into a u64
  InvalidMsgid(Value),
}

impl Error for InvalidMessage {}

impl Display for InvalidMessage {
  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    use InvalidMessage::*;

    match self {
      NotAnArray(val) => write!(fmt, "Value not an Array: '{val}'"),
      WrongArrayLength(should, is) => write!(
        fmt,
        "Array should have length {:?}, has length {}",
        should, is
      ),
      InvalidType(val) => {
        write!(fmt, "Message type not decodable into u64: {val}")
      }
      UnknownMessageType(m) => {
        write!(fmt, "Message type {m} is not 0, 1 or 2")
      }
      InvalidParams(val, s) => {
        write!(fmt, "Params of method '{s}' not an Array: '{val}'")
      }
      InvalidNotificationName(val) => write!(
        fmt,
        "Notification name not a
        string: '{}'",
        val
      ),
      InvalidRequestName(id, val) => {
        write!(fmt, "Request id {id}: name not valid String: '{val}'")
      }
      InvalidMsgid(val) => {
        write!(fmt, "Msgid of message not decodable into u64: '{val}'")
      }
    }
  }
}

/// Receiving a message from neovim failed
#[derive(Debug)]
pub enum DecodeError {
  /// Reading from the internal buffer failed.
  BufferError(RmpvDecodeError),
  /// Reading from the stream failed. This is probably unrecoverable from, but
  /// might also mean that neovim closed the stream and wants the plugin to
  /// finish. See examples/quitting.rs on how this might be caught.
  ReaderError(io::Error),
  /// Neovim sent a message that's not valid.
  InvalidMessage(InvalidMessage),
}

impl Error for DecodeError {
  fn source(&self) -> Option<&(dyn Error + 'static)> {
    match *self {
      DecodeError::BufferError(ref e) => Some(e),
      DecodeError::InvalidMessage(ref e) => Some(e),
      DecodeError::ReaderError(ref e) => Some(e),
    }
  }
}

impl Display for DecodeError {
  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    let s = match *self {
      DecodeError::BufferError(_) => "Error while reading from buffer",
      DecodeError::InvalidMessage(_) => "Error while decoding",
      DecodeError::ReaderError(_) => "Error while reading from Reader",
    };

    fmt.write_str(s)
  }
}

impl From<RmpvDecodeError> for Box<DecodeError> {
  fn from(err: RmpvDecodeError) -> Box<DecodeError> {
    Box::new(DecodeError::BufferError(err))
  }
}

impl From<InvalidMessage> for Box<DecodeError> {
  fn from(err: InvalidMessage) -> Box<DecodeError> {
    Box::new(DecodeError::InvalidMessage(err))
  }
}

impl From<io::Error> for Box<DecodeError> {
  fn from(err: io::Error) -> Box<DecodeError> {
    Box::new(DecodeError::ReaderError(err))
  }
}

/// Sending a message to neovim failed
#[derive(Debug)]
pub enum EncodeError {
  /// Encoding the message into the internal buffer has failed.
  BufferError(RmpvEncodeError),
  /// Writing the encoded message to the stream failed.
  WriterError(io::Error),
}

impl Error for EncodeError {
  fn source(&self) -> Option<&(dyn Error + 'static)> {
    match *self {
      EncodeError::BufferError(ref e) => Some(e),
      EncodeError::WriterError(ref e) => Some(e),
    }
  }
}

impl Display for EncodeError {
  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    let s = match *self {
      Self::BufferError(_) => "Error writing to buffer",
      Self::WriterError(_) => "Error writing to the Writer",
    };

    fmt.write_str(s)
  }
}

impl From<RmpvEncodeError> for Box<EncodeError> {
  fn from(err: RmpvEncodeError) -> Box<EncodeError> {
    Box::new(EncodeError::BufferError(err))
  }
}

impl From<io::Error> for Box<EncodeError> {
  fn from(err: io::Error) -> Box<EncodeError> {
    Box::new(EncodeError::WriterError(err))
  }
}

/// A [`call`](crate::neovim::Neovim::call) to neovim failed
///
/// The API functions return this, as they are just
/// proxies for [`call`](crate::neovim::Neovim::call).
#[derive(Debug)]
pub enum CallError {
  /// Sending the request to neovim has failed.
  ///
  /// Fields:
  ///
  /// 0. The underlying error
  /// 1. The name of the called method
  SendError(EncodeError, String),
  /// The internal channel to send the response to the right task was closed.
  /// This really should not happen, unless someone manages to kill individual
  /// tasks.
  ///
  /// Fields:
  ///
  /// 0. The underlying error
  /// 1. The name of the called method
  InternalReceiveError(oneshot::Canceled, String),
  /// Decoding neovim's response failed.
  ///
  /// Fields:
  ///
  /// 0. The underlying error
  /// 1. The name of the called method
  ///
  /// *Note*: DecodeError can't be Clone, so we Arc-wrap it
  DecodeError(Arc<DecodeError>, String),
  /// Neovim encountered an error while executing the reqest.
  ///
  /// Fields:
  ///
  /// 0. Neovim's error type (see `:h api`)
  /// 1. Neovim's error message
  NeovimError(Option<i64>, String),
  /// The response from neovim contained a [`Value`](rmpv::Value) of the wrong
  /// type
  WrongValueType(Value),
}

impl Error for CallError {
  fn source(&self) -> Option<&(dyn Error + 'static)> {
    match *self {
      CallError::SendError(ref e, _) => Some(e),
      CallError::InternalReceiveError(ref e, _) => Some(e),
      CallError::DecodeError(ref e, _) => Some(e.as_ref()),
      CallError::NeovimError(_, _) | CallError::WrongValueType(_) => None,
    }
  }
}

impl CallError {
  /// Determine if the error originated from a closed channel. This is generally
  /// used to close a plugin from neovim's side, and so most of the time should
  /// not be treated as a real error, but a signal to finish the program.
  #[must_use]
  pub fn is_channel_closed(&self) -> bool {
    match *self {
      CallError::SendError(EncodeError::WriterError(ref e), _)
        if e.kind() == ErrorKind::UnexpectedEof =>
      {
        return true
      }
      CallError::DecodeError(ref err, _) => {
        if let DecodeError::ReaderError(ref e) = err.as_ref() {
          if e.kind() == ErrorKind::UnexpectedEof {
            return true;
          }
        }
      }
      _ => {}
    }

    false
  }
}

impl Display for CallError {
  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    match *self {
      Self::SendError(_, ref s) => write!(fmt, "Error sending request '{s}'"),
      Self::InternalReceiveError(_, ref s) => {
        write!(fmt, "Error receiving response for '{s}'")
      }
      Self::DecodeError(_, ref s) => {
        write!(fmt, "Error decoding response to request '{s}'")
      }
      Self::NeovimError(ref i, ref s) => match i {
        Some(i) => write!(fmt, "Error processing request: {i} - '{s}')"),
        None => write!(
          fmt,
          "Error processing request, unknown error format: '{s}'"
        ),
      },
      CallError::WrongValueType(ref val) => {
        write!(fmt, "Wrong value type: '{val}'")
      }
    }
  }
}

impl From<Value> for Box<CallError> {
  fn from(val: Value) -> Box<CallError> {
    match val {
      Value::Array(mut arr)
        if arr.len() == 2 && arr[0].is_i64() && arr[1].is_str() =>
      {
        let s = arr
          .pop()
          .expect("This was checked")
          .as_str()
          .expect("This was checked")
          .into();
        let i = arr.pop().expect("This was checked").as_i64();
        Box::new(CallError::NeovimError(i, s))
      }
      val => Box::new(CallError::NeovimError(None, format!("{val:?}"))),
    }
  }
}

/// A failure in the io loop
#[derive(Debug)]
pub enum LoopError {
  /// A Msgid could not be found in the request queue
  MsgidNotFound(u64),
  /// Decoding a message failed.
  ///
  /// Fields:
  ///
  /// 0. The underlying error
  /// 1. The msgids of the requests we could not send the error to.
  ///
  /// Note: DecodeError can't be clone, so we Arc-wrap it.
  DecodeError(Arc<DecodeError>, Option<Vec<u64>>),
  /// Failed to send a Response (from neovim) through the sender from the
  /// request queue
  ///
  /// Fields:
  ///
  /// 0. The msgid of the request the response was sent for
  /// 1. The response from neovim
  InternalSendResponseError(u64, Result<Value, Value>),
}

impl Error for LoopError {
  fn source(&self) -> Option<&(dyn Error + 'static)> {
    match *self {
      LoopError::MsgidNotFound(_)
      | LoopError::InternalSendResponseError(_, _) => None,
      LoopError::DecodeError(ref e, _) => Some(e.as_ref()),
    }
  }
}

impl LoopError {
  #[must_use]
  pub fn is_channel_closed(&self) -> bool {
    if let LoopError::DecodeError(ref err, _) = *self {
      if let DecodeError::ReaderError(ref e) = err.as_ref() {
        if e.kind() == ErrorKind::UnexpectedEof {
          return true;
        }
      }
    }
    false
  }

  #[must_use]
  pub fn is_reader_error(&self) -> bool {
    if let LoopError::DecodeError(ref err, _) = *self {
      if let DecodeError::ReaderError(_) = err.as_ref() {
        return true;
      }
    }
    false
  }
}

impl Display for LoopError {
  fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    match *self {
      Self::MsgidNotFound(i) => {
        write!(fmt, "Could not find Msgid '{i}' in the Queue")
      }
      Self::DecodeError(_, ref o) => match o {
        None => write!(fmt, "Error reading message"),
        Some(v) => write!(
          fmt,
          "Error reading message, could not forward \
           error to the following requests: '{:?}'",
          v
        ),
      },
      Self::InternalSendResponseError(i, ref res) => write!(
        fmt,
        "Request {i}: Could not send response, which was {:?}",
        res
      ),
    }
  }
}

impl From<(u64, Result<Value, Value>)> for Box<LoopError> {
  fn from(res: (u64, Result<Value, Value>)) -> Box<LoopError> {
    Box::new(LoopError::InternalSendResponseError(res.0, res.1))
  }
}

impl From<(Arc<DecodeError>, Vec<u64>)> for Box<LoopError> {
  fn from(v: (Arc<DecodeError>, Vec<u64>)) -> Box<LoopError> {
    Box::new(LoopError::DecodeError(v.0, Some(v.1)))
  }
}

impl From<u64> for Box<LoopError> {
  fn from(i: u64) -> Box<LoopError> {
    Box::new(LoopError::MsgidNotFound(i))
  }
}