irc_rust/
message.rs

1use std::fmt;
2use std::fmt::{Display, Formatter};
3
4use crate::builder::Builder as MessageBuilder;
5use crate::errors::ParserError;
6use crate::parsed::Parsed;
7use crate::prefix::Prefix;
8use crate::tokenizer::{PartialCfg, Start, Tokenizer};
9use std::convert::TryFrom;
10use std::str::FromStr;
11
12/// A simple irc message containing tags, prefix, command, parameters and a trailing parameter.
13///
14/// All types returned from getters of this type ([Prefix, Params, Tags]) are owned types. So they are tied to the [Message] instance they are retrieved from and don't own their part of the message.
15///
16/// Parses its part lazily on method invokations.
17///
18/// # Examples
19///
20/// Create a Message from a plain string.
21///
22/// ```rust
23/// use irc_rust::Message;
24///
25/// # fn main() -> Result<(), irc_rust::errors::ParserError> {
26/// let message = Message::from("@key1=value1;key2=value2 :name!user@host CMD param1 param2 :trailing");
27///
28/// // Or
29///
30/// let message = "@key1=value1;key2=value2 :name!user@host CMD param1 param2 :trailing"
31///     .parse::<Message>()?;
32///
33/// assert_eq!(message.to_string(), "@key1=value1;key2=value2 :name!user@host CMD param1 param2 :trailing");
34/// # Ok(())
35/// # }
36/// ```
37///
38/// To build a message in a verbose and easy to read way you can use the `Message::builder` method and the `MessageBuilder`.
39#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub struct Message {
42    raw: String,
43}
44
45impl Message {
46    /// Returns a fully parsed but zero-copy struct referencing the parsed message.
47    pub fn parse(&self) -> Result<Parsed, ParserError> {
48        Parsed::try_from(self.raw.as_str())
49    }
50
51    /// Returns a query instance to partially parse the message.
52    ///
53    /// # Usage
54    ///
55    /// ```rust
56    /// use irc_rust::Message;
57    /// use irc_rust::tokenizer::PartialCfg;
58    /// use std::collections::HashSet;
59    /// use std::iter::FromIterator;
60    /// # fn main() -> Result<(), irc_rust::errors::ParserError> {
61    /// let message = "@tag1=value1;tag2=value2 CMD param0 param1 :trailing"
62    ///     .parse::<Message>()?;
63    /// let parsed = message.parse_partial(PartialCfg {
64    ///         tags: HashSet::from_iter(vec!["tag2"]),
65    ///         params: vec![1],
66    ///         trailing: true,
67    ///         ..PartialCfg::default()
68    ///     })?;
69    /// assert_eq!(Some("CMD"), parsed.command());
70    /// assert!(parsed.tag("tag1").is_none());
71    /// assert!(parsed.prefix().is_none());
72    /// assert_eq!(Some("value2"), parsed.tag("tag2"));
73    /// assert_eq!(Some("param1"), parsed.param(1));
74    /// # Ok(())
75    /// # }
76    /// ```
77    pub fn parse_partial<'a>(&'a self, cfg: PartialCfg<'a>) -> Result<Parsed<'a>, ParserError> {
78        Tokenizer::new(self.raw.as_str())?.parse_partial(cfg)
79    }
80
81    /// Returns a tokenizer over the message. Can be used to implement a custom parsing algorithm.
82    pub fn tokenizer(&self) -> Result<Tokenizer<Start>, ParserError> {
83        Tokenizer::new(self.raw.as_str())
84    }
85
86    /// Creates a message builder as alternative to building an irc string before creating the message.
87    pub fn builder(command: &str) -> MessageBuilder {
88        MessageBuilder::new(command)
89    }
90
91    /// Creates a builder from this message. Only initializes fields already present in the message.
92    /// By using this method a whole new Message will be created.
93    pub fn to_builder(&self) -> Result<MessageBuilder, ParserError> {
94        MessageBuilder::from_str(self.raw.as_str())
95    }
96
97    /// Returns tags if any are present.
98    pub fn tags(
99        &self,
100    ) -> Result<impl Iterator<Item = Result<(&str, &str), ParserError>>, ParserError> {
101        Tokenizer::new(self.raw.as_str()).map(|tokenizer| tokenizer.tags().into_iter())
102    }
103
104    /// Returns the Prefix if present.
105    pub fn prefix(&self) -> Result<Option<Prefix>, ParserError> {
106        Tokenizer::new(self.raw.as_str()).and_then(|tokenizer| tokenizer.prefix().parts())
107    }
108
109    /// Returns the command the message represents.
110    pub fn command(&self) -> Result<&str, ParserError> {
111        Tokenizer::new(self.raw.as_str()).and_then(|tokenizer| tokenizer.command().command())
112    }
113
114    /// Returns the params if any are present.
115    pub fn params(&self) -> Result<impl Iterator<Item = &str>, ParserError> {
116        Tokenizer::new(self.raw.as_str()).map(|tokenizer| tokenizer.params().into_iter())
117    }
118
119    /// Returns the trailing parameter if any is present.
120    pub fn trailing(&self) -> Result<Option<&str>, ParserError> {
121        Tokenizer::new(self.raw.as_str()).map(|tokenizer| tokenizer.trailing().trailing())
122    }
123}
124
125impl Display for Message {
126    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
127        self.raw.fmt(f)
128    }
129}
130
131impl FromStr for Message {
132    type Err = ParserError;
133
134    fn from_str(s: &str) -> Result<Self, Self::Err> {
135        Ok(Message::from(s.to_string()))
136    }
137}
138
139impl From<String> for Message {
140    fn from(raw: String) -> Self {
141        Message { raw }
142    }
143}
144
145impl From<&str> for Message {
146    fn from(raw: &str) -> Self {
147        Message {
148            raw: raw.to_string(),
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use crate::message::Message;
156
157    #[test]
158    #[cfg(feature = "serde")]
159    fn test_serde() {
160        let message =
161            Message::from("@test=test :user@prefix!host COMMAND param :trailing".to_string());
162        let serialized = serde_json::to_string(&message).unwrap();
163        println!("Ser: {}", serialized);
164        let deserialized: Message = serde_json::from_str(serialized.as_str()).unwrap();
165        assert_eq!(deserialized.to_string(), message.to_string());
166    }
167
168    #[test]
169    fn test_tags() {
170        let message =
171            Message::from("@test=test :user@prefix!host COMMAND param :trailing".to_string());
172        let tags = message.tags();
173        assert!(tags.is_ok(), "{:?}", tags.err());
174        let mut tags = tags.unwrap().into_iter();
175        let tag = tags.next();
176        assert!(tag.is_some(), "{:?}", tag);
177    }
178
179    #[test]
180    fn test_prefix() {
181        let message =
182            Message::from("@test=test :user@prefix!host COMMAND param :trailing".to_string());
183        let prefix = message.prefix();
184        assert!(prefix.is_ok(), "{:?}", prefix);
185        let prefix = prefix.unwrap();
186        assert!(prefix.is_some(), "{:?}", prefix);
187    }
188}