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
use std::fmt;
use std::borrow::Cow;

use pear::{ParseErr, Expected};
use parse::indexed::Context;
use parse::uri::RawInput;
use ext::IntoOwned;

/// Error emitted on URI parse failure.
///
/// Internally, the type includes information about where the parse error
/// occured (the error's context) and information about what went wrong.
/// Externally, this information can be retrieved (in textual form) through its
/// `Display` implementation. In other words, by printing a value of this type.
#[derive(Debug)]
pub struct Error<'a> {
    expected: Expected<Or<char, u8>, Cow<'a, str>, String>,
    context: Option<Context>
}

#[derive(Debug)]
enum Or<L, R> {
    A(L),
    B(R)
}

impl<'a> Error<'a> {
    crate fn from(src: &'a str, pear_error: ParseErr<RawInput<'a>>) -> Error<'a> {
        let new_expected = pear_error.expected.map(|token| {
            if token.is_ascii() && !token.is_ascii_control() {
                Or::A(token as char)
            } else {
                Or::B(token)
            }
        }, String::from_utf8_lossy, |indexed| {
            let src = Some(src.as_bytes());
            String::from_utf8_lossy(indexed.from_source(src)).to_string()
        });

        Error { expected: new_expected, context: pear_error.context }
    }

    /// Returns the byte index into the text where the error occurred if it is
    /// known.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::uri::Origin;
    ///
    /// let err = Origin::parse("/foo bar").unwrap_err();
    /// assert_eq!(err.index(), Some(4));
    /// ```
    pub fn index(&self) -> Option<usize> {
        self.context.as_ref().map(|c| c.offset)
    }
}

impl fmt::Display for Or<char, u8> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Or::A(left) => write!(f, "'{}'", left),
            Or::B(right) => write!(f, "non-ASCII byte {}", right),
        }
    }
}

impl<'a> fmt::Display for Error<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // This relies on specialization of the `Display` impl for `Expected`.
        write!(f, "{}", self.expected)?;

        if let Some(ref context) = self.context {
            write!(f, " at index {}", context.offset)?;
        }

        Ok(())
    }
}

impl<'a> IntoOwned for Error<'a> {
    type Owned = Error<'static>;

    fn into_owned(self) -> Self::Owned {
        let expected = self.expected.map(|i| i, IntoOwned::into_owned, |i| i);
        Error { expected, context: self.context }
    }
}

#[cfg(test)]
mod tests {
    use parse::uri::origin_from_str;

    macro_rules! check_err {
        ($url:expr => $error:expr) => {{
            let e = origin_from_str($url).unwrap_err();
            assert_eq!(e.to_string(), $error.to_string())
        }}
    }

    #[test]
    fn check_display() {
        check_err!("a" => "expected token '/' but found 'a' at index 0");
        check_err!("?" => "expected token '/' but found '?' at index 0");
        check_err!("这" => "expected token '/' but found non-ASCII byte 232 at index 0");
    }
}