irc_rust/
builder.rs

1use crate::errors::ParserError;
2use crate::parsed::Parsed;
3use crate::Message;
4use std::collections::HashMap;
5use std::convert::TryFrom;
6use std::str::FromStr;
7
8/// A Message Builder for a simpler generation of a message instead of building a string first.
9///
10/// # Examples
11///
12/// To build a simple message from scratch:
13///
14/// ```rust
15/// use irc_rust::Message;
16/// use std::error::Error;
17///
18/// # fn main() -> Result<(), irc_rust::errors::ParserError> {
19/// let message = Message::builder("CMD")
20///     .tag("key1", "value1")
21///     .tag("key2", "value2")
22///     .prefix("name", Some("user"), Some("host"))
23///     .param("param1").param("param2")
24///     .trailing("trailing")
25///     .build();
26///
27/// let mut tags = message.tags()?;
28/// let (key, value) = tags.next().unwrap()?;
29/// println!("{}={}", key, value); // Prints 'key1=value1'
30/// # Ok(())
31/// # }
32/// ```
33///
34/// To alter an existing message:
35///
36/// ```rust
37/// use irc_rust::Message;
38/// use std::error::Error;
39/// use irc_rust::builder::Builder;
40///
41/// # fn main() -> Result<(), Box<dyn Error>> {
42/// let message = Message::from("@key=value :name!user@host CMD param1 :trailing!").to_builder()?
43///     .tag("key", "value2")
44///     .param("param2")
45///     .param("param4")
46///     .set_param(1, "param3")
47///     .build();
48///
49/// // Or
50/// let message: Message = "@key=value :name!user@host CMD param1 :trailing!".parse::<Builder>()?
51///     .tag("key", "value2")
52///     .param("param2")
53///     .param("param4")
54///     .set_param(1, "param3")
55///     .build();
56///
57/// assert_eq!(message.to_string(), "@key=value2 :name!user@host CMD param1 param3 param4 :trailing!");
58/// Ok(())
59/// # }
60/// ```
61#[derive(Debug, Clone, Eq, PartialEq, Default)]
62pub struct Builder {
63    tags: HashMap<String, String>,
64    prefix_name: Option<String>,
65    prefix_user: Option<String>,
66    prefix_host: Option<String>,
67    command: String,
68    params: Vec<String>,
69    trailing: Option<String>,
70}
71
72impl Builder {
73    /// Creates a new empty builder.
74    ///
75    /// # Usage
76    ///
77    /// ```rust
78    /// use irc_rust::builder::Builder;
79    /// # fn main() -> Result<(), irc_rust::errors::ParserError> {
80    /// let message = Builder::new("CMD").build();
81    /// assert_eq!("CMD", message.command()?);
82    /// # Ok(())
83    /// # }
84    /// ```
85    pub fn new<S: ToString>(command: S) -> Self {
86        Builder {
87            tags: HashMap::new(),
88            prefix_name: None,
89            prefix_user: None,
90            prefix_host: None,
91            command: "".to_string(),
92            params: Vec::new(),
93            trailing: None,
94        }
95        .command(command.to_string())
96    }
97
98    /// Set the command.
99    ///
100    /// # Panics
101    ///
102    /// Panics if **cmd** is empty.
103    pub fn command<S: ToString>(mut self, cmd: S) -> Builder {
104        let cmd = cmd.to_string();
105        if cmd.is_empty() {
106            panic!("tried to set empty command");
107        }
108        self.command = cmd;
109        self
110    }
111
112    /// Set a tag.
113    ///
114    /// # Panics
115    ///
116    /// Panics if **key** is empty. **value** is allowed to be empty.
117    pub fn tag<SK: ToString, SV: ToString>(mut self, key: SK, value: SV) -> Builder {
118        let key = key.to_string();
119        if key.is_empty() {
120            panic!("tried to set tag with empty key");
121        }
122        self.tags.insert(key, value.to_string());
123        self
124    }
125
126    /// Set a prefix name.
127    ///
128    /// # Panics
129    ///
130    /// Panics if **name** is empty, **user or host** == **Some("")** or **user** is some and **host** is none.
131    pub fn prefix<SN, SU, SH>(mut self, name: SN, user: Option<SU>, host: Option<SH>) -> Builder
132    where
133        SN: ToString,
134        SU: ToString,
135        SH: ToString,
136    {
137        let name = name.to_string();
138        if name.is_empty() {
139            panic!("tried to set empty prefix name");
140        }
141        let user = user.map(|user| user.to_string());
142        if user.is_some() && user.as_ref().unwrap().is_empty() {
143            panic!("tried to set empty prefix user");
144        }
145        let host = host.map(|host| host.to_string());
146        if host.is_some() && host.as_ref().unwrap().is_empty() {
147            panic!("tried to set empty prefix host");
148        }
149        if user.is_some() && host.is_none() {
150            panic!("tried to set prefix user without host");
151        }
152        self.prefix_name = Some(name);
153        self.prefix_user = user;
154        self.prefix_host = host;
155        self
156    }
157
158    /// Add a param.
159    ///
160    /// # Panics
161    ///
162    /// Panics if **param** is empty.
163    pub fn param<S: ToString>(mut self, param: S) -> Builder {
164        let param = param.to_string();
165        if param.is_empty() {
166            panic!("tried to add empty param");
167        }
168        self.params.push(param);
169        self
170    }
171
172    /// Set a param at the given index. If the index is below 0, it won't be set.
173    /// If index >= length of the existing parameters it will be added to the end but not set as trailing.
174    /// This doesn't allow to set the trailing parameter.
175    ///
176    /// # Panics
177    ///
178    /// Panics if **param** is empty.
179    pub fn set_param<S: ToString>(mut self, index: usize, param: S) -> Builder {
180        let param = param.to_string();
181        if param.is_empty() {
182            panic!("tried to set empty param");
183        }
184        if index >= self.params.len() {
185            self.params.push(param);
186        } else {
187            self.params[index] = param;
188        }
189        self
190    }
191
192    pub fn remove_param(mut self, index: usize) -> Builder {
193        if index < self.params.len() {
194            self.params.remove(index);
195        }
196        self
197    }
198
199    //( Add a trailing param;
200    pub fn trailing<S: ToString>(mut self, trailing: S) -> Builder {
201        self.trailing = Some(trailing.to_string());
202        self
203    }
204
205    /// Create a Message instance and return if valid.
206    pub fn build(self) -> crate::message::Message {
207        let mut str = String::new();
208        if !self.tags.is_empty() {
209            str.push('@');
210            for (key, val) in self.tags {
211                str.push_str(key.as_str());
212                str.push('=');
213                str.push_str(val.as_str());
214                str.push(';')
215            }
216            str.pop();
217            str.push(' ');
218        }
219        if let Some(prefix_name) = self.prefix_name {
220            str.push(':');
221            str.push_str(prefix_name.as_str());
222            // Asserting as checked in setters.
223            assert!(self.prefix_user.is_none() || self.prefix_host.is_some());
224            if let Some(user) = self.prefix_user {
225                str.push('!');
226                str.push_str(user.as_str());
227            }
228            if let Some(host) = self.prefix_host {
229                str.push('@');
230                str.push_str(host.as_str());
231            }
232            str.push(' ')
233        }
234        str.push_str(self.command.as_str());
235        if !self.params.is_empty() {
236            str.push(' ');
237            str.push_str(&self.params.join(" "));
238        }
239        if let Some(trailing) = self.trailing {
240            str.push_str(" :");
241            str.push_str(trailing.as_str());
242        }
243        crate::message::Message::from(str)
244    }
245}
246
247impl FromStr for Builder {
248    type Err = ParserError;
249
250    fn from_str(s: &str) -> Result<Self, Self::Err> {
251        let parsed = Parsed::try_from(s)?;
252
253        let mut builder = Builder::new(parsed.command().ok_or(ParserError::NoCommand)?);
254        for (key, value) in parsed.tags() {
255            builder = builder.tag(key, value)
256        }
257        if let Some(&(name, user, host)) = parsed.prefix() {
258            builder = builder.prefix(name, user, host);
259        }
260        // Flatten to remove empty params
261        for param in parsed.params().flatten() {
262            builder = builder.param(param);
263        }
264        if let Some(trailing) = parsed.trailing() {
265            builder = builder.trailing(trailing);
266        }
267
268        Ok(builder)
269    }
270}
271
272impl TryFrom<Message> for Builder {
273    type Error = ParserError;
274
275    fn try_from(value: Message) -> Result<Self, Self::Error> {
276        value.to_builder()
277    }
278}