casper_types/contract_messages/
topics.rsuse crate::{
bytesrepr::{self, FromBytes, ToBytes},
checksummed_hex, BlockTime,
};
use core::convert::TryFrom;
use alloc::{string::String, vec::Vec};
use core::fmt::{Debug, Display, Formatter};
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(any(feature = "testing", test))]
use rand::{
distributions::{Distribution, Standard},
Rng,
};
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
use super::error::FromStrError;
pub const TOPIC_NAME_HASH_LENGTH: usize = 32;
#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(
feature = "json-schema",
derive(JsonSchema),
schemars(description = "The hash of the name of the message topic.")
)]
pub struct TopicNameHash(
#[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))]
pub [u8; TOPIC_NAME_HASH_LENGTH],
);
impl TopicNameHash {
pub const fn new(topic_name_hash: [u8; TOPIC_NAME_HASH_LENGTH]) -> TopicNameHash {
TopicNameHash(topic_name_hash)
}
pub fn value(&self) -> [u8; TOPIC_NAME_HASH_LENGTH] {
self.0
}
pub fn to_formatted_string(self) -> String {
base16::encode_lower(&self.0)
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
let bytes =
<[u8; TOPIC_NAME_HASH_LENGTH]>::try_from(checksummed_hex::decode(input)?.as_ref())?;
Ok(TopicNameHash(bytes))
}
}
impl ToBytes for TopicNameHash {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.append(&mut self.0.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
}
impl FromBytes for TopicNameHash {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (hash, rem) = FromBytes::from_bytes(bytes)?;
Ok((TopicNameHash(hash), rem))
}
}
impl Serialize for TopicNameHash {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_formatted_string().serialize(serializer)
} else {
self.0.serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for TopicNameHash {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let formatted_string = String::deserialize(deserializer)?;
TopicNameHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
} else {
let bytes = <[u8; TOPIC_NAME_HASH_LENGTH]>::deserialize(deserializer)?;
Ok(TopicNameHash(bytes))
}
}
}
impl Display for TopicNameHash {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", base16::encode_lower(&self.0))
}
}
impl Debug for TopicNameHash {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
write!(f, "MessageTopicHash({})", base16::encode_lower(&self.0))
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<TopicNameHash> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TopicNameHash {
TopicNameHash(rng.gen())
}
}
impl From<[u8; TOPIC_NAME_HASH_LENGTH]> for TopicNameHash {
fn from(value: [u8; TOPIC_NAME_HASH_LENGTH]) -> Self {
TopicNameHash(value)
}
}
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub struct MessageTopicSummary {
pub(crate) message_count: u32,
pub(crate) blocktime: BlockTime,
pub(crate) topic_name: String,
}
impl MessageTopicSummary {
pub fn new(message_count: u32, blocktime: BlockTime, topic_name: String) -> Self {
Self {
message_count,
blocktime,
topic_name,
}
}
pub fn message_count(&self) -> u32 {
self.message_count
}
pub fn blocktime(&self) -> BlockTime {
self.blocktime
}
pub fn topic_name(&self) -> String {
self.topic_name.clone()
}
}
impl ToBytes for MessageTopicSummary {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.append(&mut self.message_count.to_bytes()?);
buffer.append(&mut self.blocktime.to_bytes()?);
buffer.append(&mut self.topic_name.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.message_count.serialized_length()
+ self.blocktime.serialized_length()
+ self.topic_name.serialized_length()
}
}
impl FromBytes for MessageTopicSummary {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (message_count, rem) = FromBytes::from_bytes(bytes)?;
let (blocktime, rem) = FromBytes::from_bytes(rem)?;
let (topic_name, rem) = FromBytes::from_bytes(rem)?;
Ok((
MessageTopicSummary {
message_count,
blocktime,
topic_name,
},
rem,
))
}
}
const TOPIC_OPERATION_ADD_TAG: u8 = 0;
const OPERATION_MAX_SERIALIZED_LEN: usize = 1;
#[derive(Debug, PartialEq)]
pub enum MessageTopicOperation {
Add,
}
impl MessageTopicOperation {
pub const fn max_serialized_len() -> usize {
OPERATION_MAX_SERIALIZED_LEN
}
}
impl ToBytes for MessageTopicOperation {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
match self {
MessageTopicOperation::Add => buffer.push(TOPIC_OPERATION_ADD_TAG),
}
Ok(buffer)
}
fn serialized_length(&self) -> usize {
match self {
MessageTopicOperation::Add => 1,
}
}
}
impl FromBytes for MessageTopicOperation {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (tag, remainder): (u8, &[u8]) = FromBytes::from_bytes(bytes)?;
match tag {
TOPIC_OPERATION_ADD_TAG => Ok((MessageTopicOperation::Add, remainder)),
_ => Err(bytesrepr::Error::Formatting),
}
}
}
#[cfg(test)]
mod tests {
use crate::bytesrepr;
use super::*;
#[test]
fn serialization_roundtrip() {
let topic_name_hash = TopicNameHash::new([0x4du8; TOPIC_NAME_HASH_LENGTH]);
bytesrepr::test_serialization_roundtrip(&topic_name_hash);
let topic_summary =
MessageTopicSummary::new(10, BlockTime::new(100), "topic_name".to_string());
bytesrepr::test_serialization_roundtrip(&topic_summary);
let topic_operation = MessageTopicOperation::Add;
bytesrepr::test_serialization_roundtrip(&topic_operation);
}
#[test]
fn json_roundtrip() {
let topic_name_hash = TopicNameHash::new([0x4du8; TOPIC_NAME_HASH_LENGTH]);
let json_string = serde_json::to_string_pretty(&topic_name_hash).unwrap();
let decoded: TopicNameHash = serde_json::from_str(&json_string).unwrap();
assert_eq!(decoded, topic_name_hash);
let topic_summary =
MessageTopicSummary::new(10, BlockTime::new(100), "topic_name".to_string());
let json_string = serde_json::to_string_pretty(&topic_summary).unwrap();
let decoded: MessageTopicSummary = serde_json::from_str(&json_string).unwrap();
assert_eq!(decoded, topic_summary);
}
}