irc_rust/
parsed.rs

1use crate::errors::ParserError;
2use crate::prefix::Prefix;
3use crate::tokenizer::Tokenizer;
4use std::collections::HashMap;
5use std::convert::TryFrom;
6
7/// Fully parsed Message instead of parsing on demand. Instead of
8/// zero-allocation this struct implements zero-copy parsing.
9///
10/// Implements a partially or fully parsed message.
11///
12/// Doesn't implement the [std::convert::FromStr] trait as its lifetime
13/// depends on its source string.
14#[derive(Debug, Clone, Eq, PartialEq, Default)]
15pub struct Parsed<'a> {
16    tags: HashMap<&'a str, &'a str>,
17    prefix: Option<Prefix<'a>>,
18    command: Option<&'a str>,
19    params: Vec<Option<&'a str>>,
20    trailing: Option<&'a str>,
21}
22
23impl<'a> Parsed<'a> {
24    pub(crate) fn new(
25        tags: HashMap<&'a str, &'a str>,
26        prefix: Option<Prefix<'a>>,
27        command: Option<&'a str>,
28        params: Vec<Option<&'a str>>,
29        trailing: Option<&'a str>,
30    ) -> Self {
31        Self {
32            tags,
33            prefix,
34            command,
35            params,
36            trailing,
37        }
38    }
39
40    pub fn command(&self) -> Option<&'a str> {
41        self.command
42    }
43
44    pub fn prefix(&self) -> Option<&Prefix<'a>> {
45        self.prefix.as_ref()
46    }
47
48    pub fn params(&self) -> impl Iterator<Item = &Option<&'a str>> {
49        self.params.iter()
50    }
51
52    pub fn tags(&self) -> impl Iterator<Item = (&&'a str, &&'a str)> {
53        self.tags.iter()
54    }
55
56    pub fn tag(&self, key: &str) -> Option<&'a str> {
57        self.tags.get(key).copied()
58    }
59
60    /// Returns a param with given original index. If the [Parsed] instance
61    /// represents a partially parsed message the original index will preserve.
62    ///
63    /// # Usage
64    ///
65    /// ```rust
66    /// use irc_rust::Message;
67    /// # fn main() -> Result<(), irc_rust::errors::ParserError> {
68    /// // A fully parsed message
69    /// use irc_rust::tokenizer::PartialCfg;
70    /// let message = Message::from("CMD param0 param1 param2");
71    /// let parsed = message.parse()?;
72    /// assert_eq!(Some("param0"), parsed.param(0));
73    /// assert_eq!(Some("param1"), parsed.param(1));
74    /// assert_eq!(Some("param2"), parsed.param(2));
75    ///
76    /// // A partially parsed message preserves the original index
77    /// let message = Message::from("CMD param0 param1 param2");
78    /// let partial = message.parse_partial(PartialCfg {
79    ///         params: vec![1],
80    ///         ..Default::default()
81    ///     })?;
82    /// assert_eq!(None, partial.param(0));
83    /// assert_eq!(Some("param1"), partial.param(1));
84    /// assert_eq!(None, partial.param(2));
85    ///
86    /// # Ok(())
87    /// # }
88    /// ````
89    pub fn param(&self, index: usize) -> Option<&'a str> {
90        match self.params.get(index) {
91            Some(Some(st)) => Some(*st),
92            _ => None,
93        }
94    }
95
96    pub fn trailing(&self) -> Option<&'a str> {
97        self.trailing
98    }
99
100    pub fn prefix_name(&self) -> Option<&'a str> {
101        self.prefix.as_ref().map(|&(name, _user, _host)| name)
102    }
103
104    pub fn prefix_user(&self) -> Option<&'a str> {
105        self.prefix.as_ref().and_then(|&(_name, user, _host)| user)
106    }
107
108    pub fn prefix_host(&self) -> Option<&'a str> {
109        self.prefix.as_ref().and_then(|&(_name, _user, host)| host)
110    }
111}
112
113impl<'a> TryFrom<&'a str> for Parsed<'a> {
114    type Error = ParserError;
115
116    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
117        let mut tokenizer = Tokenizer::new(value)?.tags();
118        let iter = tokenizer.as_iter();
119        let mut tags = HashMap::new();
120        for res in iter {
121            let (key, value) = res?;
122            tags.insert(key, value);
123        }
124        let mut tokenizer = tokenizer.prefix();
125        let prefix = tokenizer
126            .parts()?
127            .map(|(name, user, host)| (name, user, host));
128        let mut tokenizer = tokenizer.command();
129        let command = tokenizer.command()?;
130        let mut tokenizer = tokenizer.params();
131        let params = tokenizer.as_iter().map(Some).collect::<Vec<_>>();
132        let trailing = tokenizer.trailing().trailing();
133
134        Ok(Self {
135            tags,
136            prefix,
137            command: Some(command),
138            params,
139            trailing,
140        })
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use crate::Message;
147    use std::error::Error;
148
149    #[test]
150    fn test_parsed() -> Result<(), Box<dyn Error>> {
151        let message = Message::builder("CMD")
152            .tag("tag1", "value1")
153            .tag("tag2", "value2")
154            .prefix("name", Some("user"), Some("host"))
155            .param("param0")
156            .trailing("Trailing Parameter!")
157            .build();
158        let parsed = message.parse()?;
159
160        assert_eq!(Some("value1"), parsed.tag("tag1"));
161        assert_eq!(Some("value2"), parsed.tag("tag2"));
162        assert_eq!(
163            Some(("name", Some("user"), Some("host"))),
164            parsed.prefix().map(|prefix| (prefix.0, prefix.1, prefix.2))
165        );
166        assert_eq!(Some("param0"), parsed.param(0));
167        assert_eq!(Some("Trailing Parameter!"), parsed.trailing());
168
169        Ok(())
170    }
171}