use std::fmt;
use borsh::{BorshDeserialize, BorshSerialize};
macro_rules! gen_byte_copy {
($dest:ident, $src:ident, $idx:literal) => {
if $src.len() > $idx {
$dest[$idx] = $src[$idx];
}
};
}
pub const TOPIC_LEN: usize = 8;
pub type TopicInner = [u8; TOPIC_LEN];
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, BorshDeserialize, BorshSerialize)]
pub struct Topic(TopicInner);
impl Topic {
pub fn new(buf: TopicInner) -> Self {
Self(buf)
}
pub fn from_str(s: &str) -> Self {
if s.as_bytes().len() > TOPIC_LEN {
panic!("malformed topic '{}'", s);
}
let mut buf = [0; TOPIC_LEN];
buf.copy_from_slice(s.as_bytes());
Self(buf)
}
pub const fn from_const_str(s: &'static str) -> Self {
let sb = s.as_bytes();
if sb.len() > TOPIC_LEN {
panic!("topic str too long");
}
let mut buf = [0u8; TOPIC_LEN];
gen_byte_copy!(buf, sb, 0);
gen_byte_copy!(buf, sb, 1);
gen_byte_copy!(buf, sb, 2);
gen_byte_copy!(buf, sb, 3);
gen_byte_copy!(buf, sb, 4);
gen_byte_copy!(buf, sb, 5);
gen_byte_copy!(buf, sb, 6);
gen_byte_copy!(buf, sb, 7);
Self(buf)
}
pub fn inner(&self) -> &TopicInner {
&self.0
}
pub fn into_inner(&self) -> TopicInner {
self.0
}
pub fn str_len(&self) -> usize {
self.0.iter().position(|v| *v == 0).unwrap_or_default()
}
pub fn as_str_bytes(&self) -> &[u8] {
&self.0[..self.str_len()]
}
pub fn is_standard(&self) -> bool {
self.as_str_bytes()
.iter()
.all(|b| b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-' || *b == b'$')
}
pub fn as_str(&self) -> Option<&str> {
if self.is_standard() {
Some(unsafe { std::str::from_utf8_unchecked(self.as_str_bytes()) })
} else {
None
}
}
}
impl fmt::Debug for Topic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ts) = self.as_str() {
f.write_str(ts)
} else {
f.write_fmt(format_args!("nonstd{:?}", self.inner()))
}
}
}
impl fmt::Display for Topic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}