mail_internals/
error.rs

1//! Module containing the `EncodingError`.
2use std::fmt::{self, Display};
3
4use failure::{Context, Fail, Backtrace};
5use ::MailType;
6
7pub const UNKNOWN: &str = "<unknown>";
8pub const UTF_8: &str = "utf-8";
9pub const US_ASCII: &str = "us-ascii";
10
11/// A general error appearing when encoding failed in some way.
12#[derive(Copy, Clone, Debug, Fail, PartialEq, Eq, Hash)]
13pub enum EncodingErrorKind {
14
15    #[fail(display = "expected <{}> text encoding {} got ",
16        expected_encoding, got_encoding)]
17    InvalidTextEncoding {
18        expected_encoding: &'static str,
19        //TODO[failure >= 0.2] make it Optional remove `UNKNOWN`
20        got_encoding: &'static str
21    },
22
23    #[fail(display = "hard line length limit breached (>= 998 bytes without CRLF)")]
24    HardLineLengthLimitBreached,
25
26    #[fail(display = "data can not be encoded with the {} encoding", encoding)]
27    NotEncodable {
28        encoding: &'static str,
29    },
30
31    #[fail(display = "malformed data")]
32    Malformed,
33
34    #[fail(display = "the mail body data cannot be accessed")]
35    AccessingMailBodyFailed,
36
37    #[fail(display = "{}", kind)]
38    Other { kind: &'static str }
39
40    //ErrorKinds potentially needed when using this wrt. to decoding the mail encoding
41    //UnsupportedEncoding { encoding: &'static str }
42}
43
44
45/// A general error appearing when encoding failed in some way.
46///
47/// This error consists of an `EncodingErrorKind` and a bit
48/// of contextual information including: The place the error
49/// happened in (`Header { name }`,`Body`), a string representing
50/// the context when it happens (e.g. the word which could not be encoded),
51/// and the mail type.
52#[derive(Debug)]
53pub struct EncodingError {
54    inner: Context<EncodingErrorKind>,
55    mail_type: Option<MailType>,
56    str_context: Option<String>,
57    place: Option<Place>
58}
59
60#[derive(Debug)]
61pub enum Place {
62    Header { name: &'static str },
63    Body
64}
65
66impl EncodingError {
67    /// Return the error kind.
68    pub fn kind(&self) -> EncodingErrorKind {
69        *self.inner.get_context()
70    }
71
72    /// Return the mail type used when the error appeared.
73    pub fn mail_type(&self) -> Option<MailType> {
74        self.mail_type
75    }
76
77    /// Returns the str_context associated with the error.
78    pub fn str_context(&self) -> Option<&str> {
79        self.str_context.as_ref().map(|s| &**s)
80    }
81
82    /// Sets the str context.
83    pub fn set_str_context<I>(&mut self, ctx: I)
84        where I: Into<String>
85    {
86        self.str_context = Some(ctx.into());
87    }
88
89    /// Returns a version of self which has a str context like the given one.
90    pub fn with_str_context<I>(mut self, ctx: I) -> Self
91        where I: Into<String>
92    {
93        self.set_str_context(ctx);
94        self
95    }
96
97    /// Adds a place (context) to self if there isn't one and returns self.
98    pub fn with_place_or_else<F>(mut self, func: F) -> Self
99        where F: FnOnce() -> Option<Place>
100    {
101        if self.place.is_none() {
102            self.place = func();
103        }
104        self
105    }
106
107    /// Adds a mail type (context) to self if there isn't one and returns self.
108    pub fn with_mail_type_or_else<F>(mut self, func: F) -> Self
109        where F: FnOnce() -> Option<MailType>
110    {
111        if self.mail_type.is_none() {
112            self.mail_type = func();
113        }
114        self
115    }
116}
117
118impl From<EncodingErrorKind> for EncodingError {
119    fn from(ctx: EncodingErrorKind) -> Self {
120        EncodingError::from(Context::new(ctx))
121    }
122}
123
124impl From<Context<EncodingErrorKind>> for EncodingError {
125    fn from(inner: Context<EncodingErrorKind>) -> Self {
126        EncodingError {
127            inner,
128            mail_type: None,
129            str_context: None,
130            place: None
131        }
132    }
133}
134
135impl From<(EncodingErrorKind, MailType)> for EncodingError {
136    fn from((ctx, mail_type): (EncodingErrorKind, MailType)) -> Self {
137        EncodingError::from((Context::new(ctx), mail_type))
138    }
139}
140
141impl From<(Context<EncodingErrorKind>, MailType)> for EncodingError {
142    fn from((inner, mail_type): (Context<EncodingErrorKind>, MailType)) -> Self {
143        EncodingError {
144            inner,
145            mail_type: Some(mail_type),
146            str_context: None,
147            place: None
148        }
149    }
150}
151
152impl Fail for EncodingError {
153
154    fn cause(&self) -> Option<&Fail> {
155        self.inner.cause()
156    }
157
158    fn backtrace(&self) -> Option<&Backtrace> {
159        self.inner.backtrace()
160    }
161}
162
163impl Display for EncodingError {
164
165    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
166        if let Some(mail_type) = self.mail_type() {
167            write!(fter, "[{:?}]", mail_type)?;
168        } else {
169            write!(fter, "[<no_mail_type>]")?;
170        }
171        Display::fmt(&self.inner, fter)
172    }
173}
174
175/// Macro for easier returning an `EncodingError`.
176///
177/// It will use the given input to create and
178/// `EncodingError` _and return it_ (like `try!`/`?`
179/// returns an error, i.e. it places a return statement
180/// in the code).
181///
182/// # Example
183///
184/// ```
185/// # #[macro_use] extern crate mail_internals;
186/// # use mail_internals::{MailType, error::EncodingError};
187/// # fn main() {
188/// #    fn failed_something() -> bool { true }
189/// #    let func = || -> Result<(), EncodingError> {
190/// if failed_something() {
191///     // Note that the `mail_type: ...` part is not required and
192///     // `EncodingErrorKind` does _not_ need to be imported.
193///     ec_bail!(mail_type: MailType::Internationalized, kind: InvalidTextEncoding {
194///         expected_encoding: "utf-8",
195///         got_encoding: "utf-32"
196///     });
197/// }
198/// #    Ok(())
199/// #    };
200/// #
201/// # }
202/// ```
203///
204#[macro_export]
205macro_rules! ec_bail {
206    (kind: $($tt:tt)*) => ({
207        return Err($crate::error::EncodingError::from(
208            $crate::error::EncodingErrorKind:: $($tt)*).into())
209    });
210    (mail_type: $mt:expr, kind: $($tt:tt)*) => ({
211        return Err($crate::error::EncodingError::from((
212            $crate::error::EncodingErrorKind:: $($tt)*,
213            $mt
214        )).into())
215    });
216}
217
218#[cfg(test)]
219mod test {
220
221    #[test]
222    fn bail_compiles_v1() {
223        let func = || -> Result<(), ::error::EncodingError> {
224            ec_bail!(kind: Other { kind: "test"});
225            #[allow(unreachable_code)] Ok(())
226        };
227        assert!((func)().is_err());
228    }
229
230    #[test]
231    fn bail_compiles_v2() {
232        fn mail_type() -> ::MailType { ::MailType::Internationalized }
233        let func = || -> Result<(), ::error::EncodingError> {
234            ec_bail!(mail_type: mail_type(), kind: Other { kind: "testicle" });
235            #[allow(unreachable_code)] Ok(())
236        };
237        assert!((func)().is_err());
238    }
239}