use std::fmt::Display;
use std::hash::Hash as StdHash;
use std::str::FromStr;
use rand::Rng;
use rand::rngs::OsRng;
use thiserror::Error;
use crate::{Hash, VerifyingKey};
pub const TOPIC_LENGTH: usize = 32;
#[derive(Clone, Copy, Debug, Ord, PartialOrd, PartialEq, Eq, StdHash)]
pub struct Topic(pub(crate) [u8; TOPIC_LENGTH]);
impl Topic {
pub fn random() -> Self {
let mut rng = OsRng;
Self::from_rng(&mut rng)
}
pub fn from_rng<R: Rng>(rng: &mut R) -> Self {
Self(rng.r#gen())
}
pub fn from_bytes(&self, bytes: &[u8]) -> Result<Self, TopicError> {
Self::try_from(bytes)
}
pub fn as_bytes(&self) -> &[u8; TOPIC_LENGTH] {
&self.0
}
pub fn to_bytes(self) -> [u8; TOPIC_LENGTH] {
self.0
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
}
impl Default for Topic {
fn default() -> Self {
Self::random()
}
}
impl From<[u8; TOPIC_LENGTH]> for Topic {
fn from(topic: [u8; TOPIC_LENGTH]) -> Self {
Self(topic)
}
}
impl From<Topic> for [u8; TOPIC_LENGTH] {
fn from(topic: Topic) -> Self {
topic.0
}
}
impl From<Hash> for Topic {
fn from(value: Hash) -> Self {
Self(*value.as_bytes())
}
}
impl From<Topic> for Hash {
fn from(topic: Topic) -> Self {
Hash::from_bytes(topic.0)
}
}
impl From<VerifyingKey> for Topic {
fn from(value: VerifyingKey) -> Self {
Self(*value.as_bytes())
}
}
impl Display for Topic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl FromStr for Topic {
type Err = TopicError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::try_from(hex::decode(value)?.as_slice())
}
}
impl TryFrom<&[u8]> for Topic {
type Error = TopicError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let value_len = value.len();
let checked_value: [u8; TOPIC_LENGTH] = value
.try_into()
.map_err(|_| TopicError::InvalidLength(value_len, TOPIC_LENGTH))?;
Ok(Self::from(checked_value))
}
}
impl TryFrom<Vec<u8>> for Topic {
type Error = TopicError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let value_len = value.len();
let checked_value: [u8; TOPIC_LENGTH] = value
.try_into()
.map_err(|_| TopicError::InvalidLength(value_len, TOPIC_LENGTH))?;
Ok(Self::from(checked_value))
}
}
#[derive(Debug, Error)]
pub enum TopicError {
#[error("invalid bytes length of {0}, expected {1} bytes")]
InvalidLength(usize, usize),
#[error("invalid hex encoding in string")]
InvalidHexEncoding(#[from] hex::FromHexError),
}