use std::{
borrow::{Borrow, BorrowMut},
io::{self, Read, Write},
ops::{Deref, DerefMut},
};
use crate::{Decodable, Encodable};
#[inline]
fn is_invalid_topic_name(topic_name: &str) -> bool {
topic_name.is_empty() || topic_name.as_bytes().len() > 65535 || topic_name.chars().any(|ch| ch == '#' || ch == '+')
}
#[derive(Debug, Eq, PartialEq, Clone, Hash, Ord, PartialOrd)]
pub struct TopicName(String);
impl TopicName {
pub fn new<S: Into<String>>(topic_name: S) -> Result<TopicName, TopicNameError> {
let topic_name = topic_name.into();
if is_invalid_topic_name(&topic_name) {
Err(TopicNameError(topic_name))
} else {
Ok(TopicName(topic_name))
}
}
pub unsafe fn new_unchecked(topic_name: String) -> TopicName {
TopicName(topic_name)
}
}
impl From<TopicName> for String {
fn from(topic_name: TopicName) -> String {
topic_name.0
}
}
impl Deref for TopicName {
type Target = TopicNameRef;
fn deref(&self) -> &TopicNameRef {
unsafe { TopicNameRef::new_unchecked(&self.0) }
}
}
impl DerefMut for TopicName {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { TopicNameRef::new_mut_unchecked(&mut self.0) }
}
}
impl Borrow<TopicNameRef> for TopicName {
fn borrow(&self) -> &TopicNameRef {
Deref::deref(self)
}
}
impl BorrowMut<TopicNameRef> for TopicName {
fn borrow_mut(&mut self) -> &mut TopicNameRef {
DerefMut::deref_mut(self)
}
}
impl Encodable for TopicName {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
(&self.0[..]).encode(writer)
}
fn encoded_length(&self) -> u32 {
(&self.0[..]).encoded_length()
}
}
impl Decodable for TopicName {
type Error = TopicNameDecodeError;
type Cond = ();
fn decode_with<R: Read>(reader: &mut R, _rest: ()) -> Result<TopicName, TopicNameDecodeError> {
let topic_name = String::decode(reader)?;
Ok(TopicName::new(topic_name)?)
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid topic filter ({0})")]
pub struct TopicNameError(pub String);
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub enum TopicNameDecodeError {
IoError(#[from] io::Error),
InvalidTopicName(#[from] TopicNameError),
}
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(transparent)]
pub struct TopicNameRef(str);
impl TopicNameRef {
pub fn new<S: AsRef<str> + ?Sized>(topic_name: &S) -> Result<&TopicNameRef, TopicNameError> {
let topic_name = topic_name.as_ref();
if is_invalid_topic_name(topic_name) {
Err(TopicNameError(topic_name.to_owned()))
} else {
Ok(unsafe { &*(topic_name as *const str as *const TopicNameRef) })
}
}
pub fn new_mut<S: AsMut<str> + ?Sized>(topic_name: &mut S) -> Result<&mut TopicNameRef, TopicNameError> {
let topic_name = topic_name.as_mut();
if is_invalid_topic_name(topic_name) {
Err(TopicNameError(topic_name.to_owned()))
} else {
Ok(unsafe { &mut *(topic_name as *mut str as *mut TopicNameRef) })
}
}
pub unsafe fn new_unchecked<S: AsRef<str> + ?Sized>(topic_name: &S) -> &TopicNameRef {
let topic_name = topic_name.as_ref();
&*(topic_name as *const str as *const TopicNameRef)
}
pub unsafe fn new_mut_unchecked<S: AsMut<str> + ?Sized>(topic_name: &mut S) -> &mut TopicNameRef {
let topic_name = topic_name.as_mut();
&mut *(topic_name as *mut str as *mut TopicNameRef)
}
pub fn is_server_specific(&self) -> bool {
self.0.starts_with('$')
}
}
impl Deref for TopicNameRef {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl ToOwned for TopicNameRef {
type Owned = TopicName;
fn to_owned(&self) -> Self::Owned {
TopicName(self.0.to_owned())
}
}
impl Encodable for TopicNameRef {
fn encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
(&self.0[..]).encode(writer)
}
fn encoded_length(&self) -> u32 {
(&self.0[..]).encoded_length()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn topic_name_sys() {
let topic_name = "$SYS".to_owned();
TopicName::new(topic_name).unwrap();
let topic_name = "$SYS/broker/connection/test.cosm-energy/state".to_owned();
TopicName::new(topic_name).unwrap();
}
#[test]
fn topic_name_slash() {
TopicName::new("/").unwrap();
}
#[test]
fn topic_name_basic() {
TopicName::new("/finance").unwrap();
TopicName::new("/finance//def").unwrap();
}
}