rutie_serde/
error.rs

1use std::fmt;
2
3use rutie::{self, Exception, Object};
4
5pub enum ErrorKind {
6    Message(String),
7    RutieException(rutie::AnyException),
8    NotImplemented(&'static str),
9}
10use self::ErrorKind::*;
11
12impl fmt::Display for ErrorKind {
13    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14        match *self {
15            Message(ref msg) => write!(f, "{}", msg),
16            RutieException(ref exception) => {
17                let inspect = exception.protect_send("inspect", &[]);
18                let msg = match inspect {
19                    Ok(inspect) => inspect
20                        .try_convert_to::<rutie::RString>()
21                        .map(|rstring| rstring.to_string())
22                        .unwrap_or_else(|_| "unexpected inspect result".to_owned()),
23                    Err(_) => "error calling inspect".to_owned(),
24                };
25                write!(f, "{}", msg)
26            }
27            NotImplemented(ref description) => write!(f, "{}", description),
28        }
29    }
30}
31
32impl fmt::Debug for ErrorKind {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        fmt::Display::fmt(self, f)
35    }
36}
37
38pub struct Error {
39    kind: ErrorKind,
40    context: Vec<String>,
41}
42
43impl Error {
44    pub fn chain_context<F, S>(mut self, func: F) -> Self
45    where
46        F: FnOnce() -> S,
47        S: Into<String>,
48    {
49        self.context.push(func().into());
50        self
51    }
52
53    fn describe_context(&self) -> String {
54        if self.context.is_empty() {
55            "".to_owned()
56        } else {
57            format!("\nContext from Rust:\n - {}", self.context.join("\n - "))
58        }
59    }
60}
61
62impl ::std::error::Error for Error {
63    fn description(&self) -> &str {
64        match self.kind {
65            Message(_) => "Generic Error",
66            RutieException(_) => "Rutie Exception",
67            NotImplemented(description) => description,
68        }
69    }
70}
71
72impl fmt::Display for Error {
73    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74        write!(f, "{:?}\n{}", self.kind, self.describe_context())
75    }
76}
77
78impl fmt::Debug for Error {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        fmt::Display::fmt(self, f)
81    }
82}
83
84impl From<ErrorKind> for Error {
85    fn from(kind: ErrorKind) -> Self {
86        Error {
87            kind,
88            context: vec![],
89        }
90    }
91}
92
93impl<'a> From<&'a str> for Error {
94    fn from(string: &'a str) -> Self {
95        ErrorKind::Message(string.into()).into()
96    }
97}
98
99impl From<String> for Error {
100    fn from(string: String) -> Self {
101        ErrorKind::Message(string).into()
102    }
103}
104impl From<rutie::AnyException> for Error {
105    fn from(exception: rutie::AnyException) -> Self {
106        ErrorKind::RutieException(exception).into()
107    }
108}
109
110// Many Rutie methods return `Result<AnyObject, AnyObject>` - we should try
111// treat the error `AnyObject` as an `AnyException`.
112impl From<rutie::AnyObject> for Error {
113    fn from(obj: rutie::AnyObject) -> Self {
114        obj.try_convert_to::<rutie::AnyException>()
115            .map(Into::into)
116            .unwrap_or_else(|_| "Error coercing AnyObject into AnyException".into())
117    }
118}
119
120impl serde::de::Error for Error {
121    fn custom<T>(msg: T) -> Self
122    where
123        T: fmt::Display,
124    {
125        format!("{}", msg).into()
126    }
127}
128
129impl serde::ser::Error for Error {
130    fn custom<T>(msg: T) -> Self
131    where
132        T: fmt::Display,
133    {
134        format!("{}", msg).into()
135    }
136}
137
138pub trait IntoException {
139    fn into_exception(self, default_class: rutie::Class) -> rutie::AnyException;
140}
141
142impl IntoException for Error {
143    fn into_exception(self, default_class: rutie::Class) -> rutie::AnyException {
144        match self.kind {
145            RutieException(ref exception) => {
146                let msg = format!("{}{}", exception.message(), self.describe_context());
147                exception.exception(Some(&msg))
148            }
149            _ => {
150                let msg = format!("{}", self);
151                let obj =
152                    default_class.new_instance(&[rutie::RString::new_utf8(&msg).to_any_object()]);
153                rutie::AnyException::from(obj.value())
154            }
155        }
156    }
157}
158
159pub type Result<T> = ::std::result::Result<T, Error>;
160
161/// This extension trait allows callers to call `.chain_context` to add extra
162/// context to errors, in the same way as error-chain's `.chain_err`. The
163/// provided context will be passed to Ruby with any Exception.
164pub trait ResultExt {
165    fn chain_context<F, S>(self, func: F) -> Self
166    where
167        F: FnOnce() -> S,
168        S: Into<String>;
169}
170
171impl<T> ResultExt for Result<T> {
172    fn chain_context<F, S>(self, func: F) -> Self
173    where
174        F: FnOnce() -> S,
175        S: Into<String>,
176    {
177        match self {
178            Ok(ok) => Ok(ok),
179            Err(err) => Err(err.chain_context(func)),
180        }
181    }
182}