<commit_before>use ascii::{AsciiStr, AsciiString, FromAsciiError};
use std::cmp::Ordering;
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;

use chrono::*;

/// Status code of a request or response.
#[derive(Eq, PartialEq, Copy, Clone, Debug, Ord, PartialOrd)]
pub struct StatusCode(pub u16);

impl StatusCode {
    /// Returns the default reason phrase for this status code.
    /// For example the status code 404 corresponds to "Not Found".
    pub fn default_reason_phrase(&self) -> &'static str {
        match self.0 {
            100 => "Continue",
            101 => "Switching Protocols",
            102 => "Processing",
            103 => "Early Hints",

            200 => "OK",
            201 => "Created",
            202 => "Accepted",
            203 => "Non-Authoritative Information",
            204 => "No Content",
            205 => "Reset Content",
            206 => "Partial Content",
            207 => "Multi-Status",
            208 => "Already Reported",
            226 => "IM Used",

            300 => "Multiple Choices",
            301 => "Moved Permanently",
            302 => "Found",
            303 => "See Other",
            304 => "Not Modified",
            305 => "Use Proxy",
            307 => "Temporary Redirect",
            308 => "Permanent Redirect",

            400 => "Bad Request",
            401 => "Unauthorized",
            402 => "Payment Required",
            403 => "Forbidden",
            404 => "Not Found",
            405 => "Method Not Allowed",
            406 => "Not Acceptable",
            407 => "Proxy Authentication Required",
            408 => "Request Timeout",
            409 => "Conflict",
            410 => "Gone",
            411 => "Length Required",
            412 => "Precondition Failed",
            413 => "Payload Too Large",
            414 => "URI Too Long",
            415 => "Unsupported Media Type",
            416 => "Range Not Satisfiable",
            417 => "Expectation Failed",
            421 => "Misdirected Request",
            422 => "Unprocessable Entity",
            423 => "Locked",
            424 => "Failed Dependency",
            426 => "Upgrade Required",
            428 => "Precondition Required",
            429 => "Too Many Requests",
            431 => "Request Header Fields Too Large",
            451 => "Unavailable For Legal Reasons",

            500 => "Internal Server Error",
            501 => "Not Implemented",
            502 => "Bad Gateway",
            503 => "Service Unavailable",
            504 => "Gateway Timeout",
            505 => "HTTP Version Not Supported",
            506 => "Variant Also Negotiates",
            507 => "Insufficient Storage",
            508 => "Loop Detected",
            510 => "Not Extended",
            511 => "Network Authentication Required",
            _ => "Unknown",
        }
    }
}

impl From<i8> for StatusCode {
    fn from(in_code: i8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u8> for StatusCode {
    fn from(in_code: u8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<i16> for StatusCode {
    fn from(in_code: i16) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u16> for StatusCode {
    fn from(in_code: u16) -> StatusCode {
        StatusCode(in_code)
    }
}

impl From<i32> for StatusCode {
    fn from(in_code: i32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u32> for StatusCode {
    fn from(in_code: u32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl AsRef<u16> for StatusCode {
    fn as_ref(&self) -> &u16 {
        &self.0
    }
}

impl PartialEq<u16> for StatusCode {
    fn eq(&self, other: &u16) -> bool {
        &self.0 == other
    }
}

impl PartialEq<StatusCode> for u16 {
    fn eq(&self, other: &StatusCode) -> bool {
        self == &other.0
    }
}

impl PartialOrd<u16> for StatusCode {
    fn partial_cmp(&self, other: &u16) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}

impl PartialOrd<StatusCode> for u16 {
    fn partial_cmp(&self, other: &StatusCode) -> Option<Ordering> {
        self.partial_cmp(&other.0)
    }
}

/// Represents a HTTP header.
#[derive(Debug, Clone)]
pub struct Header {
    pub field: HeaderField,
    pub value: AsciiString,
}

impl Header {
    /// Builds a `Header` from two `Vec<u8>`s or two `&[u8]`s.
    ///
    /// Example:
    ///
    /// ```
    /// let header = tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/plain"[..]).unwrap();
    /// ```
    #[allow(clippy::result_unit_err)]
    pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
    where
        B1: Into<Vec<u8>> + AsRef<[u8]>,
        B2: Into<Vec<u8>> + AsRef<[u8]>,
    {
        let header = HeaderField::from_bytes(header).or(Err(()))?;
        let value = AsciiString::from_ascii(value).or(Err(()))?;

        Ok(Header {
            field: header,
            value,
        })
    }
}

impl FromStr for Header {
    type Err = ();

    fn from_str(input: &str) -> Result<Header, ()> {
        let mut elems = input.splitn(2, ':');

        let field = elems.next();
        let value = elems.next();

        let (field, value) = match (field, value) {
            (Some(f), Some(v)) => (f, v),
            _ => return Err(()),
        };

        let field = match FromStr::from_str(field) {
            Ok(f) => f,
            _ => return Err(()),
        };

        let value = AsciiString::from_ascii(value.trim()).map_err(|_| ())?;

        Ok(Header { field, value })
    }
}

impl Display for Header {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}: {}", self.field, self.value.as_str())
    }
}

/// Field of a header (eg. `Content-Type`, `Content-Length`, etc.)
///
/// Comparison between two `HeaderField`s ignores case.
#[derive(Debug, Clone)]
pub struct HeaderField(AsciiString);

impl HeaderField {
    pub fn from_bytes<B>(bytes: B) -> Result<HeaderField, FromAsciiError<B>>
    where
        B: Into<Vec<u8>> + AsRef<[u8]>,
    {
        AsciiString::from_ascii(bytes).map(HeaderField)
    }

    pub fn as_str(&self) -> &AsciiStr {
        match self {
            HeaderField(ref s) => s,
        }
    }

    pub fn equiv(&self, other: &'static str) -> bool {
        other.eq_ignore_ascii_case(self.as_str().as_str())
    }
}

impl FromStr for HeaderField {
    type Err = ();

    fn from_str(s: &str) -> Result<HeaderField, ()> {
        if s.contains(char::is_whitespace) {
            Err(())
        } else {
            AsciiString::from_ascii(s).map(HeaderField).map_err(|_| ())
        }
    }
}

impl Display for HeaderField {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let method = self.as_str();
        write!(formatter, "{}", method.as_str())
    }
}

impl PartialEq for HeaderField {
    fn eq(&self, other: &HeaderField) -> bool {
        let self_str: &str = self.as_str().as_ref();
        let other_str = other.as_str().as_ref();
        self_str.eq_ignore_ascii_case(other_str)
    }
}

impl Eq for HeaderField {}

/// HTTP request methods
///
/// As per [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.1) and
/// [RFC 5789](https://tools.ietf.org/html/rfc5789)
#[derive(Debug, Clone)]
pub enum Method {
    /// `GET`
    Get,

    /// `HEAD`
    Head,

    /// `POST`
    Post,

    /// `PUT`
    Put,

    /// `DELETE`
    Delete,

    /// `CONNECT`
    Connect,

    /// `OPTIONS`
    Options,

    /// `TRACE`
    Trace,

    /// `PATCH`
    Patch,

    /// Request methods not standardized by the IETF
    NonStandard(AsciiString),
}

impl Method {
    pub fn as_str(&self) -> &str {
        match *self {
            Method::Get => "GET",
            Method::Head => "HEAD",
            Method::Post => "POST",
            Method::Put => "PUT",
            Method::Delete => "DELETE",
            Method::Connect => "CONNECT",
            Method::Options => "OPTIONS",
            Method::Trace => "TRACE",
            Method::Patch => "PATCH",
            Method::NonStandard(ref s) => s.as_str(),
        }
    }
}

impl FromStr for Method {
    type Err = ();

    fn from_str(s: &str) -> Result<Method, ()> {
        Ok(match s {
            s if s.eq_ignore_ascii_case("GET") => Method::Get,
            s if s.eq_ignore_ascii_case("HEAD") => Method::Head,
            s if s.eq_ignore_ascii_case("POST") => Method::Post,
            s if s.eq_ignore_ascii_case("PUT") => Method::Put,
            s if s.eq_ignore_ascii_case("DELETE") => Method::Delete,
            s if s.eq_ignore_ascii_case("CONNECT") => Method::Connect,
            s if s.eq_ignore_ascii_case("OPTIONS") => Method::Options,
            s if s.eq_ignore_ascii_case("TRACE") => Method::Trace,
            s if s.eq_ignore_ascii_case("PATCH") => Method::Patch,
            s => {
                let ascii_string = AsciiString::from_ascii(s).map_err(|_| ())?;
                Method::NonStandard(ascii_string)
            }
        })
    }
}

impl Display for Method {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}", self.as_str())
    }
}

impl PartialEq for Method {
    fn eq(&self, other: &Method) -> bool {
        match (self, other) {
            (&Method::NonStandard(ref s1), &Method::NonStandard(ref s2)) => {
                s1.as_str().eq_ignore_ascii_case(s2.as_str())
            }
            (&Method::Get, &Method::Get) => true,
            (&Method::Head, &Method::Head) => true,
            (&Method::Post, &Method::Post) => true,
            (&Method::Put, &Method::Put) => true,
            (&Method::Delete, &Method::Delete) => true,
            (&Method::Connect, &Method::Connect) => true,
            (&Method::Options, &Method::Options) => true,
            (&Method::Trace, &Method::Trace) => true,
            (&Method::Patch, &Method::Patch) => true,
            _ => false,
        }
    }
}

impl Eq for Method {}

/// HTTP version (usually 1.0 or 1.1).
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HTTPVersion(pub u8, pub u8);

impl Display for HTTPVersion {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let (major, minor) = match self {
            HTTPVersion(m, n) => (m, n),
        };
        write!(formatter, "{}.{}", major, minor)
    }
}

impl Ord for HTTPVersion {
    fn cmp(&self, other: &Self) -> Ordering {
        let HTTPVersion(my_major, my_minor) = *self;
        let HTTPVersion(other_major, other_minor) = *other;

        if my_major != other_major {
            return my_major.cmp(&other_major);
        }

        my_minor.cmp(&other_minor)
    }
}

impl PartialOrd for HTTPVersion {
    fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq<(u8, u8)> for HTTPVersion {
    fn eq(&self, &(major, minor): &(u8, u8)) -> bool {
        self.eq(&HTTPVersion(major, minor))
    }
}

impl PartialEq<HTTPVersion> for (u8, u8) {
    fn eq(&self, other: &HTTPVersion) -> bool {
        let &(major, minor) = self;
        HTTPVersion(major, minor).eq(other)
    }
}

impl PartialOrd<(u8, u8)> for HTTPVersion {
    fn partial_cmp(&self, &(major, minor): &(u8, u8)) -> Option<Ordering> {
        self.partial_cmp(&HTTPVersion(major, minor))
    }
}

impl PartialOrd<HTTPVersion> for (u8, u8) {
    fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
        let &(major, minor) = self;
        HTTPVersion(major, minor).partial_cmp(other)
    }
}

impl From<(u8, u8)> for HTTPVersion {
    fn from((major, minor): (u8, u8)) -> HTTPVersion {
        HTTPVersion(major, minor)
    }
}

/// Represents the current date, expressed in RFC 1123 format, e.g. Sun, 06 Nov 1994 08:49:37 GMT
#[allow(clippy::upper_case_acronyms)]
pub struct HTTPDate {
    d: DateTime<Utc>,
}

impl HTTPDate {
    pub fn new() -> HTTPDate {
        HTTPDate { d: Utc::now() }
    }
}

impl ToString for HTTPDate {
    fn to_string(&self) -> String {
        self.d.format("%a, %e %b %Y %H:%M:%S GMT").to_string()
    }
}

#[cfg(test)]
mod test {
    use super::Header;

    #[test]
    fn test_parse_header() {
        let header: Header = "Content-Type: text/html".parse().unwrap();

        assert!(header.field.equiv(&"content-type"));
        assert!(header.value.as_str() == "text/html");

        assert!("hello world".parse::<Header>().is_err());
    }

    #[test]
    fn test_parse_header_with_doublecolon() {
        let header: Header = "Time: 20: 34".parse().unwrap();

        assert!(header.field.equiv(&"time"));
        assert!(header.value.as_str() == "20: 34");
    }

    // This tests reslstance to RUSTSEC-2020-0031: "HTTP Request smuggling
    // through malformed Transfer Encoding headers"
    // (https://rustsec.org/advisories/RUSTSEC-2020-0031.html).
    #[test]
    fn test_strict_headers() {
        assert!("Transfer-Encoding : chunked".parse::<Header>().is_err());
        assert!(" Transfer-Encoding: chunked".parse::<Header>().is_err());
        assert!("Transfer Encoding: chunked".parse::<Header>().is_err());
        assert!(" Transfer\tEncoding : chunked".parse::<Header>().is_err());
        assert!("Transfer-Encoding: chunked".parse::<Header>().is_ok());
        assert!("Transfer-Encoding: chunked ".parse::<Header>().is_ok());
        assert!("Transfer-Encoding:   chunked ".parse::<Header>().is_ok());
    }
}
<commit_msg>impl Hash for Method<commit_after>use ascii::{AsciiStr, AsciiString, FromAsciiError};
use std::cmp::Ordering;
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;

use chrono::*;

/// Status code of a request or response.
#[derive(Eq, PartialEq, Copy, Clone, Debug, Ord, PartialOrd)]
pub struct StatusCode(pub u16);

impl StatusCode {
    /// Returns the default reason phrase for this status code.
    /// For example the status code 404 corresponds to "Not Found".
    pub fn default_reason_phrase(&self) -> &'static str {
        match self.0 {
            100 => "Continue",
            101 => "Switching Protocols",
            102 => "Processing",
            103 => "Early Hints",

            200 => "OK",
            201 => "Created",
            202 => "Accepted",
            203 => "Non-Authoritative Information",
            204 => "No Content",
            205 => "Reset Content",
            206 => "Partial Content",
            207 => "Multi-Status",
            208 => "Already Reported",
            226 => "IM Used",

            300 => "Multiple Choices",
            301 => "Moved Permanently",
            302 => "Found",
            303 => "See Other",
            304 => "Not Modified",
            305 => "Use Proxy",
            307 => "Temporary Redirect",
            308 => "Permanent Redirect",

            400 => "Bad Request",
            401 => "Unauthorized",
            402 => "Payment Required",
            403 => "Forbidden",
            404 => "Not Found",
            405 => "Method Not Allowed",
            406 => "Not Acceptable",
            407 => "Proxy Authentication Required",
            408 => "Request Timeout",
            409 => "Conflict",
            410 => "Gone",
            411 => "Length Required",
            412 => "Precondition Failed",
            413 => "Payload Too Large",
            414 => "URI Too Long",
            415 => "Unsupported Media Type",
            416 => "Range Not Satisfiable",
            417 => "Expectation Failed",
            421 => "Misdirected Request",
            422 => "Unprocessable Entity",
            423 => "Locked",
            424 => "Failed Dependency",
            426 => "Upgrade Required",
            428 => "Precondition Required",
            429 => "Too Many Requests",
            431 => "Request Header Fields Too Large",
            451 => "Unavailable For Legal Reasons",

            500 => "Internal Server Error",
            501 => "Not Implemented",
            502 => "Bad Gateway",
            503 => "Service Unavailable",
            504 => "Gateway Timeout",
            505 => "HTTP Version Not Supported",
            506 => "Variant Also Negotiates",
            507 => "Insufficient Storage",
            508 => "Loop Detected",
            510 => "Not Extended",
            511 => "Network Authentication Required",
            _ => "Unknown",
        }
    }
}

impl From<i8> for StatusCode {
    fn from(in_code: i8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u8> for StatusCode {
    fn from(in_code: u8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<i16> for StatusCode {
    fn from(in_code: i16) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u16> for StatusCode {
    fn from(in_code: u16) -> StatusCode {
        StatusCode(in_code)
    }
}

impl From<i32> for StatusCode {
    fn from(in_code: i32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u32> for StatusCode {
    fn from(in_code: u32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl AsRef<u16> for StatusCode {
    fn as_ref(&self) -> &u16 {
        &self.0
    }
}

impl PartialEq<u16> for StatusCode {
    fn eq(&self, other: &u16) -> bool {
        &self.0 == other
    }
}

impl PartialEq<StatusCode> for u16 {
    fn eq(&self, other: &StatusCode) -> bool {
        self == &other.0
    }
}

impl PartialOrd<u16> for StatusCode {
    fn partial_cmp(&self, other: &u16) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}

impl PartialOrd<StatusCode> for u16 {
    fn partial_cmp(&self, other: &StatusCode) -> Option<Ordering> {
        self.partial_cmp(&other.0)
    }
}

/// Represents a HTTP header.
#[derive(Debug, Clone)]
pub struct Header {
    pub field: HeaderField,
    pub value: AsciiString,
}

impl Header {
    /// Builds a `Header` from two `Vec<u8>`s or two `&[u8]`s.
    ///
    /// Example:
    ///
    /// ```
    /// let header = tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/plain"[..]).unwrap();
    /// ```
    #[allow(clippy::result_unit_err)]
    pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
    where
        B1: Into<Vec<u8>> + AsRef<[u8]>,
        B2: Into<Vec<u8>> + AsRef<[u8]>,
    {
        let header = HeaderField::from_bytes(header).or(Err(()))?;
        let value = AsciiString::from_ascii(value).or(Err(()))?;

        Ok(Header {
            field: header,
            value,
        })
    }
}

impl FromStr for Header {
    type Err = ();

    fn from_str(input: &str) -> Result<Header, ()> {
        let mut elems = input.splitn(2, ':');

        let field = elems.next();
        let value = elems.next();

        let (field, value) = match (field, value) {
            (Some(f), Some(v)) => (f, v),
            _ => return Err(()),
        };

        let field = match FromStr::from_str(field) {
            Ok(f) => f,
            _ => return Err(()),
        };

        let value = AsciiString::from_ascii(value.trim()).map_err(|_| ())?;

        Ok(Header { field, value })
    }
}

impl Display for Header {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}: {}", self.field, self.value.as_str())
    }
}

/// Field of a header (eg. `Content-Type`, `Content-Length`, etc.)
///
/// Comparison between two `HeaderField`s ignores case.
#[derive(Debug, Clone)]
pub struct HeaderField(AsciiString);

impl HeaderField {
    pub fn from_bytes<B>(bytes: B) -> Result<HeaderField, FromAsciiError<B>>
    where
        B: Into<Vec<u8>> + AsRef<[u8]>,
    {
        AsciiString::from_ascii(bytes).map(HeaderField)
    }

    pub fn as_str(&self) -> &AsciiStr {
        match self {
            HeaderField(ref s) => s,
        }
    }

    pub fn equiv(&self, other: &'static str) -> bool {
        other.eq_ignore_ascii_case(self.as_str().as_str())
    }
}

impl FromStr for HeaderField {
    type Err = ();

    fn from_str(s: &str) -> Result<HeaderField, ()> {
        if s.contains(char::is_whitespace) {
            Err(())
        } else {
            AsciiString::from_ascii(s).map(HeaderField).map_err(|_| ())
        }
    }
}

impl Display for HeaderField {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let method = self.as_str();
        write!(formatter, "{}", method.as_str())
    }
}

impl PartialEq for HeaderField {
    fn eq(&self, other: &HeaderField) -> bool {
        let self_str: &str = self.as_str().as_ref();
        let other_str = other.as_str().as_ref();
        self_str.eq_ignore_ascii_case(other_str)
    }
}

impl Eq for HeaderField {}

/// HTTP request methods
///
/// As per [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.1) and
/// [RFC 5789](https://tools.ietf.org/html/rfc5789)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Method {
    /// `GET`
    Get,

    /// `HEAD`
    Head,

    /// `POST`
    Post,

    /// `PUT`
    Put,

    /// `DELETE`
    Delete,

    /// `CONNECT`
    Connect,

    /// `OPTIONS`
    Options,

    /// `TRACE`
    Trace,

    /// `PATCH`
    Patch,

    /// Request methods not standardized by the IETF
    NonStandard(AsciiString),
}

impl Method {
    pub fn as_str(&self) -> &str {
        match *self {
            Method::Get => "GET",
            Method::Head => "HEAD",
            Method::Post => "POST",
            Method::Put => "PUT",
            Method::Delete => "DELETE",
            Method::Connect => "CONNECT",
            Method::Options => "OPTIONS",
            Method::Trace => "TRACE",
            Method::Patch => "PATCH",
            Method::NonStandard(ref s) => s.as_str(),
        }
    }
}

impl FromStr for Method {
    type Err = ();

    fn from_str(s: &str) -> Result<Method, ()> {
        Ok(match s {
            "GET" => Method::Get,
            "HEAD" => Method::Head,
            "POST" => Method::Post,
            "PUT" => Method::Put,
            "DELETE" => Method::Delete,
            "CONNECT" => Method::Connect,
            "OPTIONS" => Method::Options,
            "TRACE" => Method::Trace,
            "PATCH" => Method::Patch,
            s => {
                let ascii_string = AsciiString::from_ascii(s).map_err(|_| ())?;
                Method::NonStandard(ascii_string)
            }
        })
    }
}

impl Display for Method {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}", self.as_str())
    }
}

/// HTTP version (usually 1.0 or 1.1).
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HTTPVersion(pub u8, pub u8);

impl Display for HTTPVersion {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let (major, minor) = match self {
            HTTPVersion(m, n) => (m, n),
        };
        write!(formatter, "{}.{}", major, minor)
    }
}

impl Ord for HTTPVersion {
    fn cmp(&self, other: &Self) -> Ordering {
        let HTTPVersion(my_major, my_minor) = *self;
        let HTTPVersion(other_major, other_minor) = *other;

        if my_major != other_major {
            return my_major.cmp(&other_major);
        }

        my_minor.cmp(&other_minor)
    }
}

impl PartialOrd for HTTPVersion {
    fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq<(u8, u8)> for HTTPVersion {
    fn eq(&self, &(major, minor): &(u8, u8)) -> bool {
        self.eq(&HTTPVersion(major, minor))
    }
}

impl PartialEq<HTTPVersion> for (u8, u8) {
    fn eq(&self, other: &HTTPVersion) -> bool {
        let &(major, minor) = self;
        HTTPVersion(major, minor).eq(other)
    }
}

impl PartialOrd<(u8, u8)> for HTTPVersion {
    fn partial_cmp(&self, &(major, minor): &(u8, u8)) -> Option<Ordering> {
        self.partial_cmp(&HTTPVersion(major, minor))
    }
}

impl PartialOrd<HTTPVersion> for (u8, u8) {
    fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
        let &(major, minor) = self;
        HTTPVersion(major, minor).partial_cmp(other)
    }
}

impl From<(u8, u8)> for HTTPVersion {
    fn from((major, minor): (u8, u8)) -> HTTPVersion {
        HTTPVersion(major, minor)
    }
}

/// Represents the current date, expressed in RFC 1123 format, e.g. Sun, 06 Nov 1994 08:49:37 GMT
#[allow(clippy::upper_case_acronyms)]
pub struct HTTPDate {
    d: DateTime<Utc>,
}

impl HTTPDate {
    pub fn new() -> HTTPDate {
        HTTPDate { d: Utc::now() }
    }
}

impl ToString for HTTPDate {
    fn to_string(&self) -> String {
        self.d.format("%a, %e %b %Y %H:%M:%S GMT").to_string()
    }
}

#[cfg(test)]
mod test {
    use super::Header;

    #[test]
    fn test_parse_header() {
        let header: Header = "Content-Type: text/html".parse().unwrap();

        assert!(header.field.equiv(&"content-type"));
        assert!(header.value.as_str() == "text/html");

        assert!("hello world".parse::<Header>().is_err());
    }

    #[test]
    fn test_parse_header_with_doublecolon() {
        let header: Header = "Time: 20: 34".parse().unwrap();

        assert!(header.field.equiv(&"time"));
        assert!(header.value.as_str() == "20: 34");
    }

    // This tests reslstance to RUSTSEC-2020-0031: "HTTP Request smuggling
    // through malformed Transfer Encoding headers"
    // (https://rustsec.org/advisories/RUSTSEC-2020-0031.html).
    #[test]
    fn test_strict_headers() {
        assert!("Transfer-Encoding : chunked".parse::<Header>().is_err());
        assert!(" Transfer-Encoding: chunked".parse::<Header>().is_err());
        assert!("Transfer Encoding: chunked".parse::<Header>().is_err());
        assert!(" Transfer\tEncoding : chunked".parse::<Header>().is_err());
        assert!("Transfer-Encoding: chunked".parse::<Header>().is_ok());
        assert!("Transfer-Encoding: chunked ".parse::<Header>().is_ok());
        assert!("Transfer-Encoding:   chunked ".parse::<Header>().is_ok());
    }
}
<|endoftext|>


<commit_before>/*
Copyright ⓒ 2015 cargo-script contributors.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
/*!
This module just contains any big string literals I don't want cluttering up the rest of the code.
*/

/**
The message output when the user invokes `cargo script` with no further arguments.  We need to do this ourselves because `clap` doesn't provide any way to generate this message manually.
*/
pub const NO_ARGS_MESSAGE: &'static str = "\
The following required arguments were not supplied:
\t'<script>'

USAGE:
\tcargo script [FLAGS OPTIONS] [--] <script> <args>...

For more information try --help";

/*
What follows are the templates used to wrap script input.  The input provided by the user is inserted in place of `%b`.  *Proper* templates of some kind would be nice, but damnit, I'm lazy.
*/

/// The template used for script file inputs.
pub const FILE_TEMPLATE: &'static str = r#"%b"#;

/// The template used for `--expr` input.
pub const EXPR_TEMPLATE: &'static str = r#"
%p
fn main() {
    if let Err(e) = try_main() {
        println!("ERROR: script panicked: {:?}", e);
    }
}

fn try_main() -> Result<(), Box<std::error::Error>> {
    match ({%b}) {
        __cargo_script_expr => println!("{:?}", __cargo_script_expr)
    }
    Ok(())
}
"#;

/*
Regarding the loop templates: what I *want* is for the result of the closure to be printed to standard output *only* if it's not `()`.

* TODO: Just use TypeId, dumbass.
* TODO: Merge the `LOOP_*` templates so there isn't duplicated code.  It's icky.
*/

/// The template used for `--loop` input, assuming no `--count` flag is also given.
pub const LOOP_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        let output = closure(&line_buffer);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str) -> T, T: 'static {
    closure
}
"#;

/// The template used for `--count --loop` input.
pub const LOOP_COUNT_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    let mut count = 0;
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        count += 1;
        let output = closure(&line_buffer, count);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str, usize) -> T, T: 'static {
    closure
}
"#;

/**
The default manifest used for packages.  `%n` is replaced with the "safe name" of the input, which *should* be safe to use as a file name.
*/
pub const DEFAULT_MANIFEST: &'static str = r#"
[package]
name = "%n"
version = "0.1.0"
authors = ["Anonymous"]

[[bin]]
name = "%n"
path = "%n.rs"
"#;

/**
The name of the package metadata file.
*/
pub const METADATA_FILE: &'static str = "metadata.json";

/**
Extensions to check when trying to find script input by name.
*/
pub const SEARCH_EXTS: &'static [&'static str] = &["crs", "rs"];

/**
When generating a package's unique ID, how many hex nibbles of the digest should be used *at most*?

The largest meaningful value is `40`.
*/
pub const ID_DIGEST_LEN_MAX: usize = 16;

/**
How old can stuff in the cache be before we automatically clear it out?

Measured in milliseconds.
*/
// It's been *one week* since you looked at me,
// cocked your head to the side and said "I'm angry."
pub const MAX_CACHE_AGE_MS: u64 = 1*7*24*60*60*1000;
<commit_msg>remove unnecessary parens around expr<commit_after>/*
Copyright ⓒ 2015 cargo-script contributors.

Licensed under the MIT license (see LICENSE or <http://opensource.org
/licenses/MIT>) or the Apache License, Version 2.0 (see LICENSE of
<http://www.apache.org/licenses/LICENSE-2.0>), at your option. All
files in the project carrying such notice may not be copied, modified,
or distributed except according to those terms.
*/
/*!
This module just contains any big string literals I don't want cluttering up the rest of the code.
*/

/**
The message output when the user invokes `cargo script` with no further arguments.  We need to do this ourselves because `clap` doesn't provide any way to generate this message manually.
*/
pub const NO_ARGS_MESSAGE: &'static str = "\
The following required arguments were not supplied:
\t'<script>'

USAGE:
\tcargo script [FLAGS OPTIONS] [--] <script> <args>...

For more information try --help";

/*
What follows are the templates used to wrap script input.  The input provided by the user is inserted in place of `%b`.  *Proper* templates of some kind would be nice, but damnit, I'm lazy.
*/

/// The template used for script file inputs.
pub const FILE_TEMPLATE: &'static str = r#"%b"#;

/// The template used for `--expr` input.
pub const EXPR_TEMPLATE: &'static str = r#"
%p
fn main() {
    if let Err(e) = try_main() {
        println!("ERROR: script panicked: {:?}", e);
    }
}

fn try_main() -> Result<(), Box<std::error::Error>> {
    match {%b} {
        __cargo_script_expr => println!("{:?}", __cargo_script_expr)
    }
    Ok(())
}
"#;

/*
Regarding the loop templates: what I *want* is for the result of the closure to be printed to standard output *only* if it's not `()`.

* TODO: Just use TypeId, dumbass.
* TODO: Merge the `LOOP_*` templates so there isn't duplicated code.  It's icky.
*/

/// The template used for `--loop` input, assuming no `--count` flag is also given.
pub const LOOP_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        let output = closure(&line_buffer);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str) -> T, T: 'static {
    closure
}
"#;

/// The template used for `--count --loop` input.
pub const LOOP_COUNT_TEMPLATE: &'static str = r#"
%p
use std::any::Any;
use std::io::prelude::*;

fn main() {
    let mut closure = enforce_closure(
{%b}
    );
    let mut line_buffer = String::new();
    let mut stdin = std::io::stdin();
    let mut count = 0;
    loop {
        line_buffer.clear();
        let read_res = stdin.read_line(&mut line_buffer).unwrap_or(0);
        if read_res == 0 { break }
        count += 1;
        let output = closure(&line_buffer, count);

        let display = {
            let output_any: &Any = &output;
            !output_any.is::<()>()
        };

        if display {
            println!("{:?}", output);
        }
    }
}

fn enforce_closure<F, T>(closure: F) -> F
where F: FnMut(&str, usize) -> T, T: 'static {
    closure
}
"#;

/**
The default manifest used for packages.  `%n` is replaced with the "safe name" of the input, which *should* be safe to use as a file name.
*/
pub const DEFAULT_MANIFEST: &'static str = r#"
[package]
name = "%n"
version = "0.1.0"
authors = ["Anonymous"]

[[bin]]
name = "%n"
path = "%n.rs"
"#;

/**
The name of the package metadata file.
*/
pub const METADATA_FILE: &'static str = "metadata.json";

/**
Extensions to check when trying to find script input by name.
*/
pub const SEARCH_EXTS: &'static [&'static str] = &["crs", "rs"];

/**
When generating a package's unique ID, how many hex nibbles of the digest should be used *at most*?

The largest meaningful value is `40`.
*/
pub const ID_DIGEST_LEN_MAX: usize = 16;

/**
How old can stuff in the cache be before we automatically clear it out?

Measured in milliseconds.
*/
// It's been *one week* since you looked at me,
// cocked your head to the side and said "I'm angry."
pub const MAX_CACHE_AGE_MS: u64 = 1*7*24*60*60*1000;
<|endoftext|>


<commit_before>use nom::{alphanumeric, digit, multispace};
use nom::{IResult, Err, ErrorKind, Needed};
use std::str;
use std::str::FromStr;

use common::{column_identifier, field_list, sql_identifier, statement_terminator, table_reference,
             value_list};
use column::Column;
use table::Table;

#[derive(Clone, Debug, Default, Hash, PartialEq)]
pub struct CreateTableStatement {
    pub table: Table,
    pub fields: Vec<Column>,
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum SqlType {
    Char(u16),
    Varchar(u16),
    Int(u16),
    Bigint(u16),
    Tinyint(u16),
    Tinyblob,
    Blob,
    Double,
    Real,
    Tinytext,
    Mediumtext,
    Text,
    Date,
    Timestamp,
}

fn len_as_u16(len: &[u8]) -> u16 {
    match str::from_utf8(len) {
        Ok(s) => {
            match u16::from_str(s) {
                Ok(v) => v,
                Err(e) => panic!(e),
            }
        }
        Err(e) => panic!(e),
    }
}

/// A SQL type specifier.
named!(pub type_identifier<&[u8], SqlType>,
    alt_complete!(
          chain!(
              caseless_tag!("mediumtext"),
              || { SqlType::Mediumtext }
          )
        | chain!(
              caseless_tag!("timestamp"),
              || { SqlType::Timestamp }
          )
        | chain!(
              caseless_tag!("tinyblob"),
              || { SqlType::Tinyblob }
          )
        | chain!(
              caseless_tag!("tinytext"),
              || { SqlType::Tinytext }
          )
        | chain!(
              caseless_tag!("varchar") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              binary: opt!(caseless_tag!("binary")),
              || { SqlType::Varchar(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("tinyint") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Tinyint(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("bigint") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Bigint(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("double") ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Double }
          )
        | chain!(
              caseless_tag!("blob"),
              || { SqlType::Blob }
          )
        | chain!(
              caseless_tag!("date"),
              || { SqlType::Date }
          )
        | chain!(
              caseless_tag!("real") ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Real }
          )
        | chain!(
              caseless_tag!("text"),
              || { SqlType::Text }
          )
        | chain!(
              caseless_tag!("char") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              binary: opt!(caseless_tag!("binary")),
              || { SqlType::Char(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("int") ~
              len: opt!(delimited!(tag!("("), digit, tag!(")"))) ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Int(match len {
                  Some(len) => len_as_u16(len),
                  None => 32 as u16,
              }) }
          )
    )
);

/// Parse rule for key specifications.
named!(pub key_specification<&[u8], ()>,
    alt_complete!(
          chain!(
              caseless_tag!("primary key") ~
              multispace? ~
              column: delimited!(tag!("("), field_list, tag!(")")) ~
              opt!(complete!(chain!(
                          multispace ~
                          caseless_tag!("autoincrement"),
                          || {}
                   ))
              ),
              || {}
          )
        | chain!(
              caseless_tag!("unique key") ~
              multispace? ~
              name: opt!(sql_identifier) ~
              multispace? ~
              column: delimited!(tag!("("), field_list, tag!(")")),
              || {}
          )
    )
);

/// Parse rule for a comma-separated list.
named!(pub field_specification_list<&[u8], Vec<Column> >,
       many1!(
           complete!(chain!(
               fieldname: column_identifier ~
               fieldtype: opt!(complete!(chain!(multispace ~
                                      type_identifier ~
                                      multispace?,
                                      || {}
                               ))
               ) ~
               // XXX(malte): some of these are mutually exclusive...
               opt!(complete!(chain!(multispace? ~
                           caseless_tag!("not null") ~
                           multispace?,
                           || {}
                    ))
               ) ~
               opt!(complete!(chain!(multispace? ~
                           caseless_tag!("auto_increment") ~
                           multispace?,
                           || {}
                    ))
               ) ~
               opt!(complete!(
                       chain!(
                           multispace? ~
                           caseless_tag!("default") ~
                           multispace ~
                           alt_complete!(
                                 delimited!(tag!("'"), alphanumeric, tag!("'"))
                               | digit
                               | tag!("''")
                           ) ~
                           multispace?,
                           || {}
                       ))
               ) ~
               opt!(
                   complete!(chain!(
                       multispace? ~
                       tag!(",") ~
                       multispace?,
                       || {}
                   ))
               ),
               || { fieldname }
           ))
       )
);

/// Parse rule for a SQL CREATE TABLE query.
/// TODO(malte): support types, TEMPORARY tables, IF NOT EXISTS, AS stmt
named!(pub creation<&[u8], CreateTableStatement>,
    complete!(chain!(
        caseless_tag!("create") ~
        multispace ~
        caseless_tag!("table") ~
        multispace ~
        table: table_reference ~
        multispace ~
        fields: chain!(
                tag!("(") ~
                multispace? ~
                fields: field_specification_list ~
                multispace? ~
                opt!(key_specification) ~
                multispace? ~
                tag!(")"),
                || { fields }
            ) ~
        multispace? ~
        // XXX(malte): wrap the two below in a permutation! rule that permits arbitrary ordering
        opt!(
            complete!(
                chain!(
                    caseless_tag!("type") ~
                    multispace? ~
                    tag!("=") ~
                    multispace? ~
                    alphanumeric,
                    || {}
                )
            )
        ) ~
        multispace? ~
        opt!(
            complete!(
                chain!(
                    caseless_tag!("pack_keys") ~
                    multispace? ~
                    tag!("=") ~
                    multispace? ~
                    alt_complete!(tag!("0") | tag!("1")),
                    || {}
                )
            )
        ) ~
        statement_terminator,
        || {
            // "table AS alias" isn't legal in CREATE statements
            assert!(table.alias.is_none());
            CreateTableStatement {
                table: table,
                fields: fields,
            }
        }
    ))
);

#[cfg(test)]
mod tests {
    use super::*;
    use column::Column;
    use table::Table;

    #[test]
    fn sql_types() {
        let type0 = "bigint(20) unsigned";
        let type1 = "varchar(255) binary";

        let res = type_identifier(type0.as_bytes());
        assert_eq!(res.unwrap().1, SqlType::Bigint(20));
        let res = type_identifier(type1.as_bytes());
        assert_eq!(res.unwrap().1, SqlType::Varchar(255));
    }

    #[test]
    fn field_spec() {
        // N.B. trailing comma here because field_specification_list! doesn't handle the eof case
        // because it is never validly the end of a query
        let qstring = "id bigint(20), name varchar(255),";

        let res = field_specification_list(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   vec![Column::from("id"), Column::from("name")]);
    }

    #[test]
    fn simple_create() {
        let qstring = "CREATE TABLE users (id bigint(20), name varchar(255), email varchar(255));";

        let res = creation(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   CreateTableStatement {
                       table: Table::from("users"),
                       fields: vec![Column::from("id"),
                                    Column::from("name"),
                                    Column::from("email")],
                       ..Default::default()
                   });
    }

    #[test]
    fn mediawiki_create() {
        let qstring = "CREATE TABLE user_newtalk (  user_id int(5) NOT NULL default '0',  user_ip \
                       varchar(40) NOT NULL default '') TYPE=MyISAM;";
        let res = creation(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   CreateTableStatement {
                       table: Table::from("user_newtalk"),
                       fields: vec![Column::from("user_id"), Column::from("user_ip")],
                       ..Default::default()
                   });
    }
}
<commit_msg>Return keys from CREATE statements<commit_after>use nom::{alphanumeric, digit, multispace};
use nom::{IResult, Err, ErrorKind, Needed};
use std::str;
use std::str::FromStr;

use common::{column_identifier, field_list, sql_identifier, statement_terminator, table_reference,
             value_list};
use column::Column;
use table::Table;

#[derive(Clone, Debug, Default, Hash, PartialEq)]
pub struct CreateTableStatement {
    pub table: Table,
    pub fields: Vec<Column>,
    pub keys: Option<Vec<TableKey>>,
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum SqlType {
    Char(u16),
    Varchar(u16),
    Int(u16),
    Bigint(u16),
    Tinyint(u16),
    Tinyblob,
    Blob,
    Double,
    Real,
    Tinytext,
    Mediumtext,
    Text,
    Date,
    Timestamp,
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum TableKey {
    PrimaryKey(Vec<Column>),
    UniqueKey(Option<String>, Vec<Column>),
    Key(String, Vec<Column>),
}

fn len_as_u16(len: &[u8]) -> u16 {
    match str::from_utf8(len) {
        Ok(s) => {
            match u16::from_str(s) {
                Ok(v) => v,
                Err(e) => panic!(e),
            }
        }
        Err(e) => panic!(e),
    }
}

/// A SQL type specifier.
named!(pub type_identifier<&[u8], SqlType>,
    alt_complete!(
          chain!(
              caseless_tag!("mediumtext"),
              || { SqlType::Mediumtext }
          )
        | chain!(
              caseless_tag!("timestamp"),
              || { SqlType::Timestamp }
          )
        | chain!(
              caseless_tag!("tinyblob"),
              || { SqlType::Tinyblob }
          )
        | chain!(
              caseless_tag!("tinytext"),
              || { SqlType::Tinytext }
          )
        | chain!(
              caseless_tag!("varchar") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              binary: opt!(caseless_tag!("binary")),
              || { SqlType::Varchar(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("tinyint") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Tinyint(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("bigint") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Bigint(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("double") ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Double }
          )
        | chain!(
              caseless_tag!("blob"),
              || { SqlType::Blob }
          )
        | chain!(
              caseless_tag!("date"),
              || { SqlType::Date }
          )
        | chain!(
              caseless_tag!("real") ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Real }
          )
        | chain!(
              caseless_tag!("text"),
              || { SqlType::Text }
          )
        | chain!(
              caseless_tag!("char") ~
              len: delimited!(tag!("("), digit, tag!(")")) ~
              multispace? ~
              binary: opt!(caseless_tag!("binary")),
              || { SqlType::Char(len_as_u16(len)) }
          )
        | chain!(
              caseless_tag!("int") ~
              len: opt!(delimited!(tag!("("), digit, tag!(")"))) ~
              multispace? ~
              signed: opt!(alt_complete!(caseless_tag!("unsigned") | caseless_tag!("signed"))),
              || { SqlType::Int(match len {
                  Some(len) => len_as_u16(len),
                  None => 32 as u16,
              }) }
          )
    )
);

/// Parse rule for an individual key specification.
named!(pub key_specification<&[u8], TableKey>,
    alt_complete!(
          chain!(
              caseless_tag!("primary key") ~
              multispace? ~
              columns: delimited!(tag!("("), field_list, tag!(")")) ~
              opt!(complete!(chain!(
                          multispace ~
                          caseless_tag!("autoincrement"),
                          || { }
                   ))
              ),
              || { TableKey::PrimaryKey(columns) }
          )
        | chain!(
              caseless_tag!("unique key") ~
              multispace? ~
              name: opt!(sql_identifier) ~
              multispace? ~
              columns: delimited!(tag!("("), field_list, tag!(")")),
              || {
                  match name {
                      Some(name) => {
                          let n = String::from(str::from_utf8(name).unwrap());
                          TableKey::UniqueKey(Some(n), columns)
                      },
                      None => TableKey::UniqueKey(None, columns),
                  }
              }
          )
    )
);

/// Parse rule for a comma-separated list.
named!(pub key_specification_list<&[u8], Vec<TableKey>>,
       many1!(
           complete!(chain!(
               key: key_specification ~
               opt!(
                   complete!(chain!(
                       multispace? ~
                       tag!(",") ~
                       multispace?,
                       || {}
                   ))
               ),
               || { key }
           ))
       )
);

/// Parse rule for a comma-separated list.
named!(pub field_specification_list<&[u8], Vec<Column> >,
       many1!(
           complete!(chain!(
               fieldname: column_identifier ~
               fieldtype: opt!(complete!(chain!(multispace ~
                                      type_identifier ~
                                      multispace?,
                                      || {}
                               ))
               ) ~
               // XXX(malte): some of these are mutually exclusive...
               opt!(complete!(chain!(multispace? ~
                           caseless_tag!("not null") ~
                           multispace?,
                           || {}
                    ))
               ) ~
               opt!(complete!(chain!(multispace? ~
                           caseless_tag!("auto_increment") ~
                           multispace?,
                           || {}
                    ))
               ) ~
               opt!(complete!(
                       chain!(
                           multispace? ~
                           caseless_tag!("default") ~
                           multispace ~
                           alt_complete!(
                                 delimited!(tag!("'"), alphanumeric, tag!("'"))
                               | digit
                               | tag!("''")
                           ) ~
                           multispace?,
                           || {}
                       ))
               ) ~
               opt!(
                   complete!(chain!(
                       multispace? ~
                       tag!(",") ~
                       multispace?,
                       || {}
                   ))
               ),
               || { fieldname }
           ))
       )
);

/// Parse rule for a SQL CREATE TABLE query.
/// TODO(malte): support types, TEMPORARY tables, IF NOT EXISTS, AS stmt
named!(pub creation<&[u8], CreateTableStatement>,
    complete!(chain!(
        caseless_tag!("create") ~
        multispace ~
        caseless_tag!("table") ~
        multispace ~
        table: table_reference ~
        multispace ~
        tag!("(") ~
        multispace? ~
        fields: field_specification_list ~
        multispace? ~
        keys: opt!(key_specification_list) ~
        multispace? ~
        tag!(")") ~
        multispace? ~
        // XXX(malte): wrap the two below in a permutation! rule that permits arbitrary ordering
        opt!(
            complete!(
                chain!(
                    caseless_tag!("type") ~
                    multispace? ~
                    tag!("=") ~
                    multispace? ~
                    alphanumeric,
                    || {}
                )
            )
        ) ~
        multispace? ~
        opt!(
            complete!(
                chain!(
                    caseless_tag!("pack_keys") ~
                    multispace? ~
                    tag!("=") ~
                    multispace? ~
                    alt_complete!(tag!("0") | tag!("1")),
                    || {}
                )
            )
        ) ~
        statement_terminator,
        || {
            // "table AS alias" isn't legal in CREATE statements
            assert!(table.alias.is_none());
            CreateTableStatement {
                table: table,
                fields: fields,
                keys: keys,
            }
        }
    ))
);

#[cfg(test)]
mod tests {
    use super::*;
    use column::Column;
    use table::Table;

    #[test]
    fn sql_types() {
        let type0 = "bigint(20) unsigned";
        let type1 = "varchar(255) binary";

        let res = type_identifier(type0.as_bytes());
        assert_eq!(res.unwrap().1, SqlType::Bigint(20));
        let res = type_identifier(type1.as_bytes());
        assert_eq!(res.unwrap().1, SqlType::Varchar(255));
    }

    #[test]
    fn field_spec() {
        // N.B. trailing comma here because field_specification_list! doesn't handle the eof case
        // because it is never validly the end of a query
        let qstring = "id bigint(20), name varchar(255),";

        let res = field_specification_list(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   vec![Column::from("id"), Column::from("name")]);
    }

    #[test]
    fn simple_create() {
        let qstring = "CREATE TABLE users (id bigint(20), name varchar(255), email varchar(255));";

        let res = creation(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   CreateTableStatement {
                       table: Table::from("users"),
                       fields: vec![Column::from("id"),
                                    Column::from("name"),
                                    Column::from("email")],
                       ..Default::default()
                   });
    }

    #[test]
    fn mediawiki_create() {
        let qstring = "CREATE TABLE user_newtalk (  user_id int(5) NOT NULL default '0',  user_ip \
                       varchar(40) NOT NULL default '') TYPE=MyISAM;";
        let res = creation(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   CreateTableStatement {
                       table: Table::from("user_newtalk"),
                       fields: vec![Column::from("user_id"), Column::from("user_ip")],
                       ..Default::default()
                   });
    }

    #[test]
    fn keys() {
        // simple primary key
        let qstring = "CREATE TABLE users (id bigint(20), name varchar(255), email varchar(255), \
                       PRIMARY KEY (id));";

        let res = creation(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   CreateTableStatement {
                       table: Table::from("users"),
                       fields: vec![Column::from("id"),
                                    Column::from("name"),
                                    Column::from("email")],
                       keys: Some(vec![TableKey::PrimaryKey(vec![Column::from("id")])]),
                       ..Default::default()
                   });

        // named unique key
        let qstring = "CREATE TABLE users (id bigint(20), name varchar(255), email varchar(255), \
                       UNIQUE KEY id_k (id));";

        let res = creation(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   CreateTableStatement {
                       table: Table::from("users"),
                       fields: vec![Column::from("id"),
                                    Column::from("name"),
                                    Column::from("email")],
                       keys: Some(vec![TableKey::UniqueKey(Some(String::from("id_k")),
                                                           vec![Column::from("id")])]),
                       ..Default::default()
                   });
    }
}
<|endoftext|>


<commit_before>/// The Toml Delete extensions

use toml::Value;

use tokenizer::Token;
use tokenizer::tokenize_with_seperator;
use error::*;

pub trait TomlValueDeleteExt {

    /// Extension function for deleting a value in the current toml::Value document
    /// using a custom seperator.
    ///
    /// # Semantics
    ///
    /// The function does _not_ delete non-empty data structures. So deleting `array` from
    ///
    /// ```toml
    /// array = [ 1 ]
    /// ```
    ///
    /// does _not_ work.
    ///
    /// # Return value
    ///
    /// If the delete operation worked correctly, `Ok(Option<Value>)` is returned.
    ///
    /// The `Option<Value>` part is `None` if no value was actually removed as there was no value
    /// there. For example, if you're deleting `table.a` and the Table `table` has no key `a`, then
    /// `Ok(None)` is returned. Also, if you're deleting from an Array, but there is nothing in the
    /// array, or the array is shorter than the index you're deleting.
    /// If the delete operation actually removed something from the toml document, this value is
    /// returned as `Ok(Some(Value))`.
    ///
    /// On failure, `Err(e)` is returned
    ///
    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>>;

    /// Extension function for inserting a value from the current toml::Value document
    ///
    /// See documentation of `TomlValueinsertExt::insert_with_seperator`
    fn delete(&mut self, query: &String) -> Result<Option<Value>> {
        self.delete_with_seperator(query, '.')
    }

}

impl TomlValueDeleteExt for Value {

    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>> {
        use resolver::mut_resolver::resolve;
        use std::ops::Index;

        let mut tokens = try!(tokenize_with_seperator(query, sep));
        let last_token = tokens.pop_last();

        /// Check whether a structure (Table/Array) is empty. If the Value has not these types,
        /// the default value is returned
        #[inline]
        fn is_empty(val: Option<&Value>, default: bool) -> bool {
            val.map(|v| match v {
                    &Value::Table(ref tab) => tab.is_empty(),
                    &Value::Array(ref arr) => arr.is_empty(),
                    _                      => default
                })
                .unwrap_or(default)
        }

        #[inline]
        fn is_table(val: Option<&Value>) -> bool {
            val.map(|v| is_match!(v, &Value::Table(_))).unwrap_or(false)
        }

        #[inline]
        fn is_array(val: Option<&Value>) -> bool {
            val.map(|v| is_match!(v, &Value::Array(_))).unwrap_or(false)
        }

        #[inline]
        fn name_of_val(val: Option<&Value>) -> &'static str {
            val.map(|v| match v {
                &Value::Array(_)    => "Array",
                &Value::Boolean(_)  => "Boolean",
                &Value::Datetime(_) => "Datetime",
                &Value::Float(_)    => "Float",
                &Value::Integer(_)  => "Integer",
                &Value::String(_)   => "String",
                &Value::Table(_)    => "Table",
            }).unwrap_or("None")
        }

        if last_token.is_none() {
            match self {
                &mut Value::Table(ref mut tab) => {
                    match tokens {
                        Token::Identifier { ident, .. } => {
                            if is_empty(tab.get(&ident), true) {
                                Ok(tab.remove(&ident))
                            } else {
                                if is_table(tab.get(&ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyTable(ident.clone());
                                    Err(Error::from(kind))
                                } else if is_array(tab.get(&ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyArray(ident.clone());
                                    Err(Error::from(kind))
                                } else {
                                    let act = name_of_val(tab.get(&ident));
                                    let tbl = "table";
                                    let k   = ErrorKind::CannotAccessBecauseTypeMismatch(tbl, act);
                                    Err(Error::from(k))
                                }
                            }
                        },
                        _ => Ok(None)
                    }
                },
                &mut Value::Array(ref mut arr) => {
                    match tokens {
                        Token::Identifier { ident, .. } => {
                            let ek = ErrorKind::NoIdentifierInArray(ident);
                            Err(Error::from(ek))
                        },
                        Token::Index { idx , .. } => {
                            if is_empty(Some(arr.index(idx)), true) {
                                Ok(Some(arr.remove(idx)))
                            } else {
                                unimplemented!()
                            }
                        },
                    }
                },
                _ => {
                    let kind = match tokens {
                        Token::Identifier { ident, .. } => ErrorKind::QueryingValueAsTable(ident),
                        Token::Index { idx , .. } => ErrorKind::QueryingValueAsArray(idx),
                    };
                    Err(Error::from(kind))
                }
            }
        } else {
            let mut val = try!(resolve(self, &tokens));
            let last_token = last_token.unwrap();
            match val {
                &mut Value::Table(ref mut tab) => {
                    match *last_token {
                        Token::Identifier { ref ident, .. } => {
                            if is_empty(tab.get(ident), true) {
                                Ok(tab.remove(ident))
                            } else {
                                if is_table(tab.get(ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyTable(ident.clone());
                                    Err(Error::from(kind))
                                } else if is_array(tab.get(ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyArray(ident.clone());
                                    Err(Error::from(kind))
                                } else {
                                    let act = name_of_val(tab.get(ident));
                                    let tbl = "table";
                                    let k   = ErrorKind::CannotAccessBecauseTypeMismatch(tbl, act);
                                    Err(Error::from(k))
                                }
                            }
                        },
                        Token::Index { idx, .. } => {
                            unimplemented!()
                        },
                    }
                },
                &mut Value::Array(ref mut arr) => {
                    match *last_token {
                        Token::Identifier { ref ident, .. } => {
                            unimplemented!()
                        },
                        Token::Index { idx, .. } => {
                            if is_empty(Some(&arr.index(idx)), true) {
                                Ok(Some(arr.remove(idx)))
                            } else {
                                unimplemented!()
                            }
                        },
                    }
                },
                _ => {
                    unimplemented!()
                }
            }
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use error::*;
    use toml::Value;
    use toml::from_str as toml_from_str;

    #[test]
    fn test_delete_from_empty_document() {
        let mut toml : Value = toml_from_str("").unwrap();

        let res = toml.delete_with_seperator(&String::from("a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_from_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_integer() {
        let mut toml : Value = toml_from_str(r#"
        value = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Integer(1)));
    }

    #[test]
    fn test_delete_string() {
        let mut toml : Value = toml_from_str(r#"
        value = "foo"
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::String(_)));
        match res {
            Value::String(ref s) => assert_eq!("foo", s),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Table(_)));
        match res {
            Value::Table(ref t) => assert!(t.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = []
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Array(_)));
        match res {
            Value::Array(ref a) => assert!(a.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_nonempty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        a = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyTable(_)));
    }

    #[test]
    fn test_delete_nonempty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyArray(_)));
    }

    #[test]
    fn test_delete_int_from_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        int = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.int"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_array_from_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        array = []
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.array"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Array(_))));
    }

    #[test]
    fn test_delete_int_from_array_from_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.array.[0]"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_int_from_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array.[0]"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_int_from_table_from_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ { table = { int = 1 } } ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array.[0].table.int"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_from_array_value() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let mut ary = toml.read_mut(&String::from("array")).unwrap();
        let res     = ary.delete_with_seperator(&String::from("[0]"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_from_int_value() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let mut ary = toml.read_mut(&String::from("array.[0]")).unwrap();
        let res     = ary.delete_with_seperator(&String::from("nonexist"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::QueryingValueAsTable(_)));
    }

    #[test]
    fn test_delete_index_from_non_array() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = 1
        "#).unwrap();

        let mut ary = toml.read_mut(&String::from("array")).unwrap();
        let res     = ary.delete_with_seperator(&String::from("[0]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::QueryingValueAsArray(_)));
    }

    #[test]
    fn test_delete_index_from_table_in_table() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        table = { another = { int = 1 } }
        "#).unwrap();

        let res     = toml.delete_with_seperator(&String::from("table.another.[0]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::NoIndexInTable(0)));
    }

    #[test]
    fn test_delete_identifier_from_array_in_table() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        table = { another = [ 1, 2, 3, 4, 5, 6 ] }
        "#).unwrap();

        let res     = toml.delete_with_seperator(&String::from("table.another.nonexist"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::NoIdentifierInArray(_)));
    }

    #[test]
    fn test_delete_nonexistent_array_idx() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = [ 1, 2, 3 ]
        "#).unwrap();

        let res     = toml.delete_with_seperator(&String::from("array.[22]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::ArrayIndexOutOfBounds(22, 3)));
    }

}

<commit_msg>Impl Value::delete_with_seperator to fix test_delete_index_from_table_in_table<commit_after>/// The Toml Delete extensions

use toml::Value;

use tokenizer::Token;
use tokenizer::tokenize_with_seperator;
use error::*;

pub trait TomlValueDeleteExt {

    /// Extension function for deleting a value in the current toml::Value document
    /// using a custom seperator.
    ///
    /// # Semantics
    ///
    /// The function does _not_ delete non-empty data structures. So deleting `array` from
    ///
    /// ```toml
    /// array = [ 1 ]
    /// ```
    ///
    /// does _not_ work.
    ///
    /// # Return value
    ///
    /// If the delete operation worked correctly, `Ok(Option<Value>)` is returned.
    ///
    /// The `Option<Value>` part is `None` if no value was actually removed as there was no value
    /// there. For example, if you're deleting `table.a` and the Table `table` has no key `a`, then
    /// `Ok(None)` is returned. Also, if you're deleting from an Array, but there is nothing in the
    /// array, or the array is shorter than the index you're deleting.
    /// If the delete operation actually removed something from the toml document, this value is
    /// returned as `Ok(Some(Value))`.
    ///
    /// On failure, `Err(e)` is returned
    ///
    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>>;

    /// Extension function for inserting a value from the current toml::Value document
    ///
    /// See documentation of `TomlValueinsertExt::insert_with_seperator`
    fn delete(&mut self, query: &String) -> Result<Option<Value>> {
        self.delete_with_seperator(query, '.')
    }

}

impl TomlValueDeleteExt for Value {

    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>> {
        use resolver::mut_resolver::resolve;
        use std::ops::Index;

        let mut tokens = try!(tokenize_with_seperator(query, sep));
        let last_token = tokens.pop_last();

        /// Check whether a structure (Table/Array) is empty. If the Value has not these types,
        /// the default value is returned
        #[inline]
        fn is_empty(val: Option<&Value>, default: bool) -> bool {
            val.map(|v| match v {
                    &Value::Table(ref tab) => tab.is_empty(),
                    &Value::Array(ref arr) => arr.is_empty(),
                    _                      => default
                })
                .unwrap_or(default)
        }

        #[inline]
        fn is_table(val: Option<&Value>) -> bool {
            val.map(|v| is_match!(v, &Value::Table(_))).unwrap_or(false)
        }

        #[inline]
        fn is_array(val: Option<&Value>) -> bool {
            val.map(|v| is_match!(v, &Value::Array(_))).unwrap_or(false)
        }

        #[inline]
        fn name_of_val(val: Option<&Value>) -> &'static str {
            val.map(|v| match v {
                &Value::Array(_)    => "Array",
                &Value::Boolean(_)  => "Boolean",
                &Value::Datetime(_) => "Datetime",
                &Value::Float(_)    => "Float",
                &Value::Integer(_)  => "Integer",
                &Value::String(_)   => "String",
                &Value::Table(_)    => "Table",
            }).unwrap_or("None")
        }

        if last_token.is_none() {
            match self {
                &mut Value::Table(ref mut tab) => {
                    match tokens {
                        Token::Identifier { ident, .. } => {
                            if is_empty(tab.get(&ident), true) {
                                Ok(tab.remove(&ident))
                            } else {
                                if is_table(tab.get(&ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyTable(ident.clone());
                                    Err(Error::from(kind))
                                } else if is_array(tab.get(&ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyArray(ident.clone());
                                    Err(Error::from(kind))
                                } else {
                                    let act = name_of_val(tab.get(&ident));
                                    let tbl = "table";
                                    let k   = ErrorKind::CannotAccessBecauseTypeMismatch(tbl, act);
                                    Err(Error::from(k))
                                }
                            }
                        },
                        _ => Ok(None)
                    }
                },
                &mut Value::Array(ref mut arr) => {
                    match tokens {
                        Token::Identifier { ident, .. } => {
                            let ek = ErrorKind::NoIdentifierInArray(ident);
                            Err(Error::from(ek))
                        },
                        Token::Index { idx , .. } => {
                            if is_empty(Some(arr.index(idx)), true) {
                                Ok(Some(arr.remove(idx)))
                            } else {
                                unimplemented!()
                            }
                        },
                    }
                },
                _ => {
                    let kind = match tokens {
                        Token::Identifier { ident, .. } => ErrorKind::QueryingValueAsTable(ident),
                        Token::Index { idx , .. } => ErrorKind::QueryingValueAsArray(idx),
                    };
                    Err(Error::from(kind))
                }
            }
        } else {
            let mut val = try!(resolve(self, &tokens));
            let last_token = last_token.unwrap();
            match val {
                &mut Value::Table(ref mut tab) => {
                    match *last_token {
                        Token::Identifier { ref ident, .. } => {
                            if is_empty(tab.get(ident), true) {
                                Ok(tab.remove(ident))
                            } else {
                                if is_table(tab.get(ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyTable(ident.clone());
                                    Err(Error::from(kind))
                                } else if is_array(tab.get(ident)) {
                                    let kind = ErrorKind::CannotDeleteNonEmptyArray(ident.clone());
                                    Err(Error::from(kind))
                                } else {
                                    let act = name_of_val(tab.get(ident));
                                    let tbl = "table";
                                    let k   = ErrorKind::CannotAccessBecauseTypeMismatch(tbl, act);
                                    Err(Error::from(k))
                                }
                            }
                        },
                        Token::Index { idx, .. } => {
                            let kind = ErrorKind::NoIndexInTable(idx);
                            Err(Error::from(kind))
                        },
                    }
                },
                &mut Value::Array(ref mut arr) => {
                    match *last_token {
                        Token::Identifier { ref ident, .. } => {
                            unimplemented!()
                        },
                        Token::Index { idx, .. } => {
                            if is_empty(Some(&arr.index(idx)), true) {
                                Ok(Some(arr.remove(idx)))
                            } else {
                                unimplemented!()
                            }
                        },
                    }
                },
                _ => {
                    unimplemented!()
                }
            }
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use error::*;
    use toml::Value;
    use toml::from_str as toml_from_str;

    #[test]
    fn test_delete_from_empty_document() {
        let mut toml : Value = toml_from_str("").unwrap();

        let res = toml.delete_with_seperator(&String::from("a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_from_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_integer() {
        let mut toml : Value = toml_from_str(r#"
        value = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Integer(1)));
    }

    #[test]
    fn test_delete_string() {
        let mut toml : Value = toml_from_str(r#"
        value = "foo"
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::String(_)));
        match res {
            Value::String(ref s) => assert_eq!("foo", s),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Table(_)));
        match res {
            Value::Table(ref t) => assert!(t.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = []
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Array(_)));
        match res {
            Value::Array(ref a) => assert!(a.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_nonempty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        a = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyTable(_)));
    }

    #[test]
    fn test_delete_nonempty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyArray(_)));
    }

    #[test]
    fn test_delete_int_from_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        int = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.int"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_array_from_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        array = []
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.array"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Array(_))));
    }

    #[test]
    fn test_delete_int_from_array_from_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.array.[0]"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_int_from_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array.[0]"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_int_from_table_from_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ { table = { int = 1 } } ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array.[0].table.int"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_from_array_value() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let mut ary = toml.read_mut(&String::from("array")).unwrap();
        let res     = ary.delete_with_seperator(&String::from("[0]"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(is_match!(res, Some(Value::Integer(1))));
    }

    #[test]
    fn test_delete_from_int_value() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let mut ary = toml.read_mut(&String::from("array.[0]")).unwrap();
        let res     = ary.delete_with_seperator(&String::from("nonexist"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::QueryingValueAsTable(_)));
    }

    #[test]
    fn test_delete_index_from_non_array() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = 1
        "#).unwrap();

        let mut ary = toml.read_mut(&String::from("array")).unwrap();
        let res     = ary.delete_with_seperator(&String::from("[0]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::QueryingValueAsArray(_)));
    }

    #[test]
    fn test_delete_index_from_table_in_table() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        table = { another = { int = 1 } }
        "#).unwrap();

        let res     = toml.delete_with_seperator(&String::from("table.another.[0]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::NoIndexInTable(0)));
    }

    #[test]
    fn test_delete_identifier_from_array_in_table() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        table = { another = [ 1, 2, 3, 4, 5, 6 ] }
        "#).unwrap();

        let res     = toml.delete_with_seperator(&String::from("table.another.nonexist"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::NoIdentifierInArray(_)));
    }

    #[test]
    fn test_delete_nonexistent_array_idx() {
        use read::TomlValueReadExt;

        let mut toml : Value = toml_from_str(r#"
        array = [ 1, 2, 3 ]
        "#).unwrap();

        let res     = toml.delete_with_seperator(&String::from("array.[22]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::ArrayIndexOutOfBounds(22, 3)));
    }

}

<|endoftext|>