use std::{
collections::HashMap,
fmt,
ops::{Deref, DerefMut}
};
use bytes::{BufMut, BytesMut};
use crate::err::Error;
use super::{params::Params, validators::validate_topic};
#[derive(Debug, Clone)]
pub struct Telegram {
topic: String,
params: Params
}
impl Deref for Telegram {
type Target = Params;
fn deref(&self) -> &Self::Target {
&self.params
}
}
impl DerefMut for Telegram {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.params
}
}
impl AsRef<str> for Telegram {
fn as_ref(&self) -> &str {
&self.topic
}
}
impl Telegram {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn new(topic: impl ToString) -> Self {
let topic = topic.to_string();
assert!(validate_topic(&topic).is_ok());
Self {
topic,
params: Params::default()
}
}
pub fn try_new(topic: impl Into<String>) -> Result<Self, Error> {
let topic = topic.into();
validate_topic(&topic)?;
Ok(Self {
topic,
params: Params::default()
})
}
pub fn with_params(topic: impl Into<String>, params: Params) -> Self {
let topic = topic.into();
validate_topic(&topic).unwrap();
Self { topic, params }
}
#[allow(clippy::needless_pass_by_value)]
pub fn try_with_params(
topic: impl ToString,
params: Params
) -> Result<Self, Error> {
let topic = topic.to_string();
validate_topic(&topic)?;
Ok(Self { topic, params })
}
pub(crate) fn new_uninit() -> Self {
Self {
topic: String::new(),
params: Params::default()
}
}
#[must_use]
pub fn num_params(&self) -> usize {
self.params.len()
}
#[must_use]
pub const fn params(&self) -> &Params {
&self.params
}
pub const fn params_mut(&mut self) -> &mut Params {
&mut self.params
}
#[must_use]
pub const fn get_params_inner(&self) -> &HashMap<String, String> {
self.params.inner()
}
pub fn set_topic(&mut self, topic: &str) -> Result<(), Error> {
validate_topic(topic)?;
self.topic = topic.to_string();
Ok(())
}
#[must_use]
pub fn get_topic(&self) -> &str {
self.topic.as_ref()
}
#[must_use]
pub fn calc_buf_size(&self) -> usize {
let mut size = 0;
size += self.topic.len() + 1;
size + self.params.calc_buf_size()
}
pub fn serialize(&self) -> Result<Vec<u8>, Error> {
let mut buf = Vec::new();
if self.topic.is_empty() {
return Err(Error::BadFormat("Missing heading".to_string()));
}
let b = self.topic.as_bytes();
for a in b {
buf.push(*a);
}
buf.push(b'\n');
for (key, value) in self.get_params_inner() {
let k = key.as_bytes();
let v = value.as_bytes();
for a in k {
buf.push(*a);
}
buf.push(b' ');
for a in v {
buf.push(*a);
}
buf.push(b'\n');
}
buf.push(b'\n');
Ok(buf)
}
pub fn encoder_write(&self, buf: &mut BytesMut) -> Result<(), Error> {
if self.topic.is_empty() {
return Err(Error::SerializeError("Missing Telegram topic".to_string()));
}
let size = self.calc_buf_size();
buf.reserve(size);
buf.put(self.topic.as_bytes());
buf.put_u8(b'\n');
for (key, value) in self.get_params_inner() {
buf.put(key.as_bytes());
buf.put_u8(b' ');
buf.put(value.as_bytes());
buf.put_u8(b'\n');
}
buf.put_u8(b'\n');
Ok(())
}
#[must_use]
pub fn into_params(self) -> Params {
self.params
}
#[must_use]
pub fn unwrap_topic_params(self) -> (String, Params) {
(self.topic, self.params)
}
}
impl From<String> for Telegram {
fn from(topic: String) -> Self {
Self {
topic,
params: Params::default()
}
}
}
impl TryFrom<(&str, Params)> for Telegram {
type Error = Error;
fn try_from(t: (&str, Params)) -> Result<Self, Self::Error> {
validate_topic(t.0)?;
Ok(Self {
topic: t.0.to_string(),
params: t.1
})
}
}
impl TryFrom<(String, Params)> for Telegram {
type Error = Error;
fn try_from(t: (String, Params)) -> Result<Self, Self::Error> {
validate_topic(&t.0)?;
Ok(Self {
topic: t.0,
params: t.1
})
}
}
impl fmt::Display for Telegram {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.topic, self.params)
}
}
#[cfg(test)]
mod tests {
use super::{Error, Params, Telegram};
#[test]
fn simple() {
let mut tg = Telegram::new("SomeTopic");
assert_eq!(tg.get_topic(), "SomeTopic");
tg.add_str("Foo", "bar").unwrap();
assert_eq!(tg.get_str("Foo").unwrap(), "bar");
assert_eq!(tg.get_str("Moo"), None);
}
#[test]
fn exist() {
let mut tg = Telegram::new("SomeTopic");
tg.add_str("foo", "bar").unwrap();
assert!(tg.contains("foo"));
assert!(!tg.contains("nonexistent"));
}
#[test]
fn integer() {
let mut tg = Telegram::new("SomeTopic");
assert_eq!(tg.get_topic(), "SomeTopic");
tg.add_str("Num", "64").unwrap();
assert_eq!(tg.get_fromstr::<u16, _>("Num").unwrap().unwrap(), 64);
}
#[test]
fn size() {
let mut tg = Telegram::new("SomeTopic");
tg.add_param("Num", 7_usize).unwrap();
assert_eq!(tg.get_fromstr::<usize, _>("Num").unwrap().unwrap(), 7);
}
#[test]
fn intoparams() {
let mut tg = Telegram::new("SomeTopic");
tg.add_str("Foo", "bar").unwrap();
assert_eq!(tg.get_str("Foo").unwrap(), "bar");
assert_eq!(tg.get_str("Moo"), None);
let params = tg.into_params();
let val = params.get_str("Foo");
assert_eq!(val.unwrap(), "bar");
}
#[test]
fn display() {
let mut tg = Telegram::new("hello");
tg.add_param("foo", "bar").unwrap();
let s = format!("{tg}");
assert_eq!(s, "hello:{foo=bar}");
}
#[test]
fn ser_size() {
let mut tg = Telegram::new("hello");
tg.add_str("foo", "bar").unwrap();
tg.add_str("moo", "cow").unwrap();
let sz = tg.calc_buf_size();
assert_eq!(sz, 6 + 8 + 8 + 1);
}
#[test]
fn bad_topic_leading() {
let mut tg = Telegram::new("Hello");
let Err(Error::BadFormat(msg)) = tg.set_topic(" SomeTopic") else {
panic!("Unexpectedly not Error::BadFormat");
};
assert_eq!(msg, "Invalid leading topic character");
}
#[test]
fn bad_topic() {
let mut tg = Telegram::new("Hello");
let Err(Error::BadFormat(msg)) = tg.set_topic("Some Topic") else {
panic!("Unexpectedly not Error::BadFormat");
};
assert_eq!(msg, "Invalid topic character");
}
#[test]
fn create_from_tuple() {
let mut params = Params::new();
params.add_str("my", "word").unwrap();
let tg = Telegram::try_from(("Hello", params)).unwrap();
assert_eq!(tg.get_str("my"), Some("word"));
}
}