1use std::{
6 collections::HashMap,
7 fmt,
8 ops::{Deref, DerefMut}
9};
10
11use bytes::{BufMut, BytesMut};
12
13use crate::err::Error;
14
15use super::{params::Params, validators::validate_topic};
16
17#[derive(Debug, Clone)]
23pub struct Telegram {
24 topic: String,
25 params: Params
26}
27
28impl Deref for Telegram {
29 type Target = Params;
30
31 fn deref(&self) -> &Self::Target {
32 &self.params
33 }
34}
35
36impl DerefMut for Telegram {
37 fn deref_mut(&mut self) -> &mut Self::Target {
38 &mut self.params
39 }
40}
41
42impl AsRef<str> for Telegram {
43 fn as_ref(&self) -> &str {
44 &self.topic
45 }
46}
47
48impl Telegram {
49 #[must_use]
54 #[allow(clippy::needless_pass_by_value)]
55 pub fn new(topic: impl ToString) -> Self {
56 let topic = topic.to_string();
57 assert!(validate_topic(&topic).is_ok());
58 Self {
59 topic,
60 params: Params::default()
61 }
62 }
63
64 pub fn try_new(topic: impl Into<String>) -> Result<Self, Error> {
69 let topic = topic.into();
70 validate_topic(&topic)?;
71 Ok(Self {
72 topic,
73 params: Params::default()
74 })
75 }
76
77 pub fn with_params(topic: impl Into<String>, params: Params) -> Self {
82 let topic = topic.into();
83 validate_topic(&topic).unwrap();
84 Self { topic, params }
85 }
86
87 #[allow(clippy::needless_pass_by_value)]
92 pub fn try_with_params(
93 topic: impl ToString,
94 params: Params
95 ) -> Result<Self, Error> {
96 let topic = topic.to_string();
97 validate_topic(&topic)?;
98 Ok(Self { topic, params })
99 }
100
101 pub(crate) fn new_uninit() -> Self {
105 Self {
106 topic: String::new(),
107 params: Params::default()
108 }
109 }
110
111 #[must_use]
126 pub fn num_params(&self) -> usize {
127 self.params.len()
128 }
129
130 #[must_use]
132 pub const fn params(&self) -> &Params {
133 &self.params
134 }
135
136 pub const fn params_mut(&mut self) -> &mut Params {
148 &mut self.params
149 }
150
151 #[must_use]
156 pub const fn get_params_inner(&self) -> &HashMap<String, String> {
157 self.params.inner()
158 }
159
160 pub fn set_topic(&mut self, topic: &str) -> Result<(), Error> {
178 validate_topic(topic)?;
179 self.topic = topic.to_string();
180 Ok(())
181 }
182
183 #[must_use]
193 pub fn get_topic(&self) -> &str {
194 self.topic.as_ref()
195 }
196
197 #[must_use]
205 pub fn calc_buf_size(&self) -> usize {
206 let mut size = 0;
208 size += self.topic.len() + 1; size + self.params.calc_buf_size()
212 }
213
214 pub fn serialize(&self) -> Result<Vec<u8>, Error> {
219 let mut buf = Vec::new();
220
221 if self.topic.is_empty() {
222 return Err(Error::BadFormat("Missing heading".to_string()));
223 }
224
225 let b = self.topic.as_bytes();
227 for a in b {
228 buf.push(*a);
229 }
230 buf.push(b'\n');
231
232 for (key, value) in self.get_params_inner() {
233 let k = key.as_bytes();
234 let v = value.as_bytes();
235 for a in k {
236 buf.push(*a);
237 }
238 buf.push(b' ');
239 for a in v {
240 buf.push(*a);
241 }
242 buf.push(b'\n');
243 }
244
245 buf.push(b'\n');
246
247 Ok(buf)
248 }
249
250 pub fn encoder_write(&self, buf: &mut BytesMut) -> Result<(), Error> {
255 if self.topic.is_empty() {
256 return Err(Error::SerializeError("Missing Telegram topic".to_string()));
257 }
258
259 let size = self.calc_buf_size();
261
262 buf.reserve(size);
264
265 buf.put(self.topic.as_bytes());
267 buf.put_u8(b'\n');
268
269 for (key, value) in self.get_params_inner() {
270 buf.put(key.as_bytes());
271 buf.put_u8(b' ');
272 buf.put(value.as_bytes());
273 buf.put_u8(b'\n');
274 }
275 buf.put_u8(b'\n');
276
277 Ok(())
278 }
279
280 #[must_use]
282 pub fn into_params(self) -> Params {
283 self.params
284 }
285
286 #[must_use]
288 pub fn unwrap_topic_params(self) -> (String, Params) {
289 (self.topic, self.params)
290 }
291}
292
293impl From<String> for Telegram {
294 fn from(topic: String) -> Self {
295 Self {
296 topic,
297 params: Params::default()
298 }
299 }
300}
301
302impl TryFrom<(&str, Params)> for Telegram {
303 type Error = Error;
304
305 fn try_from(t: (&str, Params)) -> Result<Self, Self::Error> {
306 validate_topic(t.0)?;
307 Ok(Self {
308 topic: t.0.to_string(),
309 params: t.1
310 })
311 }
312}
313
314impl TryFrom<(String, Params)> for Telegram {
315 type Error = Error;
316
317 fn try_from(t: (String, Params)) -> Result<Self, Self::Error> {
318 validate_topic(&t.0)?;
319 Ok(Self {
320 topic: t.0,
321 params: t.1
322 })
323 }
324}
325
326impl fmt::Display for Telegram {
327 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
328 write!(f, "{}:{}", self.topic, self.params)
329 }
330}
331
332
333#[cfg(test)]
334mod tests {
335 use super::{Error, Params, Telegram};
336
337 #[test]
338 fn simple() {
339 let mut tg = Telegram::new("SomeTopic");
340 assert_eq!(tg.get_topic(), "SomeTopic");
341
342 tg.add_str("Foo", "bar").unwrap();
343 assert_eq!(tg.get_str("Foo").unwrap(), "bar");
344
345 assert_eq!(tg.get_str("Moo"), None);
346 }
347
348 #[test]
349 fn exist() {
350 let mut tg = Telegram::new("SomeTopic");
351
352 tg.add_str("foo", "bar").unwrap();
353 assert!(tg.contains("foo"));
354
355 assert!(!tg.contains("nonexistent"));
356 }
357
358 #[test]
359 fn integer() {
360 let mut tg = Telegram::new("SomeTopic");
361
362 assert_eq!(tg.get_topic(), "SomeTopic");
363
364 tg.add_str("Num", "64").unwrap();
365 assert_eq!(tg.get_fromstr::<u16, _>("Num").unwrap().unwrap(), 64);
366 }
367
368 #[test]
369 fn size() {
370 let mut tg = Telegram::new("SomeTopic");
371
372 tg.add_param("Num", 7_usize).unwrap();
373 assert_eq!(tg.get_fromstr::<usize, _>("Num").unwrap().unwrap(), 7);
374 }
375
376 #[test]
377 fn intoparams() {
378 let mut tg = Telegram::new("SomeTopic");
379
380 tg.add_str("Foo", "bar").unwrap();
381 assert_eq!(tg.get_str("Foo").unwrap(), "bar");
382 assert_eq!(tg.get_str("Moo"), None);
383
384 let params = tg.into_params();
385 let val = params.get_str("Foo");
386 assert_eq!(val.unwrap(), "bar");
387 }
388
389 #[test]
390 fn display() {
391 let mut tg = Telegram::new("hello");
392
393 tg.add_param("foo", "bar").unwrap();
394 let s = format!("{tg}");
395 assert_eq!(s, "hello:{foo=bar}");
396 }
397
398 #[test]
399 fn ser_size() {
400 let mut tg = Telegram::new("hello");
401
402 tg.add_str("foo", "bar").unwrap();
403 tg.add_str("moo", "cow").unwrap();
404
405 let sz = tg.calc_buf_size();
406
407 assert_eq!(sz, 6 + 8 + 8 + 1);
408 }
409
410 #[test]
411 fn bad_topic_leading() {
412 let mut tg = Telegram::new("Hello");
413 let Err(Error::BadFormat(msg)) = tg.set_topic(" SomeTopic") else {
414 panic!("Unexpectedly not Error::BadFormat");
415 };
416 assert_eq!(msg, "Invalid leading topic character");
417 }
418
419 #[test]
420 fn bad_topic() {
421 let mut tg = Telegram::new("Hello");
422 let Err(Error::BadFormat(msg)) = tg.set_topic("Some Topic") else {
423 panic!("Unexpectedly not Error::BadFormat");
424 };
425 assert_eq!(msg, "Invalid topic character");
426 }
427
428 #[test]
429 fn create_from_tuple() {
430 let mut params = Params::new();
431 params.add_str("my", "word").unwrap();
432 let tg = Telegram::try_from(("Hello", params)).unwrap();
433
434 assert_eq!(tg.get_str("my"), Some("word"));
435 }
436}
437
438