dbus-message-parser 4.3.1

Libary to encode and decode DBus message
Documentation
use super::MatchRuleError;
use std::iter::Iterator;

pub struct Split<'a>(&'a str);

impl Split<'_> {
    pub(super) fn new(match_rules: &str) -> Split<'_> {
        Split(match_rules)
    }
}

enum Input {
    AlphanumericAndUnderscore,
    Equal,
    Comma,
    Quote,
    Backslash,
    Others,
}

impl From<char> for Input {
    fn from(c: char) -> Self {
        if c.is_ascii_alphanumeric() || c == '_' {
            Input::AlphanumericAndUnderscore
        } else if c == '=' {
            Input::Equal
        } else if c == ',' {
            Input::Comma
        } else if c == '\'' {
            Input::Quote
        } else if c == '\\' {
            Input::Backslash
        } else {
            Input::Others
        }
    }
}

enum State {
    KeyBegin,
    Key,
    Value,
    ValueBackslash,
    ValueInsideQuotes,
}

fn next_position(string: &str) -> Result<usize, MatchRuleError> {
    let mut state = State::KeyBegin;
    for (position, c) in string.char_indices() {
        let i = Input::from(c);
        match state {
            State::KeyBegin => match i {
                Input::AlphanumericAndUnderscore => state = State::Key,
                Input::Equal => return Err(MatchRuleError::KeyEmpty),
                _ => return Err(MatchRuleError::KeyInvalidChar(c)),
            },
            State::Key => match i {
                Input::AlphanumericAndUnderscore => state = State::Key,
                Input::Equal => state = State::Value,
                _ => return Err(MatchRuleError::KeyInvalidChar(c)),
            },
            State::Value => match i {
                Input::Backslash => state = State::ValueBackslash,
                Input::Quote => state = State::ValueInsideQuotes,
                Input::Comma => return Ok(position),
                _ => (),
            },
            State::ValueBackslash => match i {
                Input::Backslash => state = State::ValueBackslash,
                Input::Comma => return Ok(position),
                _ => state = State::Value,
            },
            State::ValueInsideQuotes => {
                if let Input::Quote = i {
                    state = State::Value;
                }
            }
        }
    }

    match state {
        State::KeyBegin => Err(MatchRuleError::KeyEmpty),
        State::Key => Err(MatchRuleError::MissingEqual),
        State::ValueBackslash => Ok(string.len()),
        State::ValueInsideQuotes => Err(MatchRuleError::ValueClosingQuote),
        State::Value => Ok(string.len()),
    }
}

impl<'a> Iterator for Split<'a> {
    type Item = Result<&'a str, MatchRuleError>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.0.is_empty() {
            return None;
        }

        match next_position(self.0) {
            Ok(position) => {
                let (result, string) = self.0.split_at(position);
                if let Some(string) = string.strip_prefix(',') {
                    self.0 = string;
                } else {
                    self.0 = string;
                }
                Some(Ok(result))
            }
            Err(e) => Some(Err(e)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{next_position, MatchRuleError, Split};

    #[test]
    fn split_1() {
        let mut split = Split::new("a=a,b=b");
        assert_eq!(split.next(), Some(Ok("a=a")));
        assert_eq!(split.next(), Some(Ok("b=b")));
        assert_eq!(split.next(), None)
    }

    #[test]
    fn split_2() {
        let mut split = Split::new("a=''\\,b=b");
        assert_eq!(split.next(), Some(Ok("a=''\\")));
        assert_eq!(split.next(), Some(Ok("b=b")));
        assert_eq!(split.next(), None)
    }

    #[test]
    fn split_3() {
        let mut split = Split::new("a=',\\',b=b");
        assert_eq!(split.next(), Some(Ok("a=',\\'")));
        assert_eq!(split.next(), Some(Ok("b=b")));
        assert_eq!(split.next(), None)
    }

    #[test]
    fn split_4() {
        let mut split = Split::new("a=\\\\");
        assert_eq!(split.next(), Some(Ok("a=\\\\")));
        assert_eq!(split.next(), None);
    }

    #[test]
    fn split_5() {
        let mut split = Split::new("a=\\a");
        assert_eq!(split.next(), Some(Ok("a=\\a")));
        assert_eq!(split.next(), None);
    }

    #[test]
    fn split_error_1() {
        let mut split = Split::new("=");
        assert_eq!(split.next(), Some(Err(MatchRuleError::KeyEmpty)))
    }

    #[test]
    fn split_error_2() {
        let mut split = Split::new("a");
        assert_eq!(split.next(), Some(Err(MatchRuleError::MissingEqual)))
    }

    #[test]
    fn split_error_3() {
        let mut split = Split::new("a='");
        assert_eq!(split.next(), Some(Err(MatchRuleError::ValueClosingQuote)))
    }

    #[test]
    fn split_error_4() {
        let mut split = Split::new("\\='");
        assert_eq!(
            split.next(),
            Some(Err(MatchRuleError::KeyInvalidChar('\\')))
        )
    }

    #[test]
    fn split_error_5() {
        let mut split = Split::new("a\\='");
        assert_eq!(
            split.next(),
            Some(Err(MatchRuleError::KeyInvalidChar('\\')))
        )
    }

    #[test]
    fn next_position_error() {
        assert_eq!(next_position(""), Err(MatchRuleError::KeyEmpty))
    }
}