nvim_rs/rpc/
unpack.rs

1//! Tools to unpack a [`Value`](rmpv::Value) to something we can use.
2//!
3//! Conversion is fallible, so [`try_unpack`](self::TryUnpack::try_unpack)
4//! returns the [`Value`](rmpv::Value) if it is not of the correct type.
5//!
6//! ### Usage
7//!
8//! ```
9//! use rmpv::Value;
10//! use nvim_rs::rpc::unpack::TryUnpack;
11//!
12//! let v = Value::from("hoodle");
13//! let s:String = v.try_unpack().unwrap();
14//!
15//! assert_eq!(String::from("hoodle"), s);
16//! ```
17use rmpv::Value;
18
19/// Trait to allow seamless conversion from a [`Value`](rmpv::Value) to the type
20/// it contains. In particular, this should never panic.
21///
22/// This is basically a specialized variant of `TryInto<V> for Value`.
23pub trait TryUnpack<V> {
24  /// Returns the value contained in `self`.
25  ///
26  /// # Errors
27  ///
28  /// Returns `Err(self)` if `self` does not contain a value of type `V`.
29  fn try_unpack(self) -> Result<V, Value>;
30}
31
32/// This is needed because the blanket impl `TryFrom<Value> for Value` uses
33/// `Error = !`.
34impl TryUnpack<Value> for Value {
35  fn try_unpack(self) -> Result<Value, Value> {
36    Ok(self)
37  }
38}
39
40impl TryUnpack<()> for Value {
41  fn try_unpack(self) -> Result<(), Value> {
42    if self.is_nil() {
43      Ok(())
44    } else {
45      Err(self)
46    }
47  }
48}
49
50// TODO: Replace this when rmpv implements `TryFrom<Value> for String`.
51impl TryUnpack<String> for Value {
52  fn try_unpack(self) -> Result<String, Value> {
53    match self {
54      Value::String(s) if s.is_str() => {
55        Ok(s.into_str().expect("This was valid UTF8"))
56      }
57      val => Err(val),
58    }
59  }
60}
61
62impl TryUnpack<(i64, i64)> for Value {
63  fn try_unpack(self) -> Result<(i64, i64), Value> {
64    if let Value::Array(ref v) = self {
65      if v.len() == 2 {
66        let mut vi = v.iter().map(Value::as_i64);
67        if let (Some(Some(i)), Some(Some(j))) = (vi.next(), vi.next()) {
68          return Ok((i, j));
69        }
70      }
71    }
72    Err(self)
73  }
74}
75
76/// The bound `Value: From<T>` is necessary so we can recover the values if one
77/// of the elements could not be unpacked. In practice, though, we only
78/// implement `TryUnpack<T>` in those cases anyways.
79impl<T> TryUnpack<Vec<T>> for Value
80where
81  Value: TryUnpack<T> + From<T>,
82{
83  fn try_unpack(self) -> Result<Vec<T>, Value> {
84    match self {
85      Value::Array(v) => {
86        let mut newvec = vec![];
87        let mut vi = v.into_iter();
88
89        while let Some(ele) = vi.next() {
90          match ele.try_unpack() {
91            Ok(t) => newvec.push(t),
92            Err(ele) => {
93              let mut restorevec: Vec<Value> =
94                newvec.into_iter().map(Value::from).collect();
95              restorevec.push(ele);
96              restorevec.extend(vi);
97              return Err(Value::Array(restorevec));
98            }
99          }
100        }
101        Ok(newvec)
102      }
103      val => Err(val),
104    }
105  }
106}
107
108macro_rules! impl_try_unpack_tryfrom {
109  ($t: ty) => {
110    impl TryUnpack<$t> for Value {
111      fn try_unpack(self) -> Result<$t, Value> {
112        use std::convert::TryInto;
113        self.try_into()
114      }
115    }
116  };
117}
118
119impl_try_unpack_tryfrom!(i64);
120impl_try_unpack_tryfrom!(bool);
121impl_try_unpack_tryfrom!(Vec<(Value, Value)>);