1use std::fmt;
2
3use borsh::{BorshDeserialize, BorshSerialize};
4
5macro_rules! gen_byte_copy {
8 ($dest:ident, $src:ident, $idx:literal) => {
9 if $src.len() > $idx {
10 $dest[$idx] = $src[$idx];
11 }
12 };
13}
14
15pub const TOPIC_LEN: usize = 8;
16
17pub type TopicInner = [u8; TOPIC_LEN];
19
20#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, BorshDeserialize, BorshSerialize)]
22pub struct Topic(TopicInner);
23
24impl Topic {
25 pub fn new(buf: TopicInner) -> Self {
27 Self(buf)
28 }
29
30 pub fn from_str(s: &str) -> Self {
36 if s.as_bytes().len() > TOPIC_LEN {
37 panic!("malformed topic '{}'", s);
38 }
39
40 let mut buf = [0; TOPIC_LEN];
41 buf.copy_from_slice(s.as_bytes());
42
43 Self(buf)
44 }
45
46 pub const fn from_const_str(s: &'static str) -> Self {
47 let sb = s.as_bytes();
48
49 if sb.len() > TOPIC_LEN {
50 panic!("topic str too long");
51 }
52
53 let mut buf = [0u8; TOPIC_LEN];
54
55 gen_byte_copy!(buf, sb, 0);
57 gen_byte_copy!(buf, sb, 1);
58 gen_byte_copy!(buf, sb, 2);
59 gen_byte_copy!(buf, sb, 3);
60 gen_byte_copy!(buf, sb, 4);
61 gen_byte_copy!(buf, sb, 5);
62 gen_byte_copy!(buf, sb, 6);
63 gen_byte_copy!(buf, sb, 7);
64
65 Self(buf)
66 }
67
68 pub fn inner(&self) -> &TopicInner {
69 &self.0
70 }
71
72 pub fn into_inner(&self) -> TopicInner {
73 self.0
74 }
75
76 pub fn str_len(&self) -> usize {
78 self.0.iter().position(|v| *v == 0).unwrap_or_default()
79 }
80
81 pub fn as_str_bytes(&self) -> &[u8] {
83 &self.0[..self.str_len()]
84 }
85
86 pub fn is_standard(&self) -> bool {
88 let str_bytes = self.str_len();
89
90 self.as_str_bytes().iter().all(|b| {
92 b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-' || *b == b'$' || *b == b'.'
93 }) && self.inner()[str_bytes..].iter().all(|b| *b == 0)
94 }
95
96 pub fn as_str(&self) -> Option<&str> {
98 if self.is_standard() {
99 Some(unsafe { std::str::from_utf8_unchecked(self.as_str_bytes()) })
100 } else {
101 None
102 }
103 }
104}
105
106impl fmt::Debug for Topic {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 if let Some(ts) = self.as_str() {
109 f.write_str(ts)
110 } else {
111 f.write_fmt(format_args!("nonstd{:?}", self.inner()))
112 }
113 }
114}
115
116impl fmt::Display for Topic {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 <Self as fmt::Debug>::fmt(self, f)
119 }
120}