use crate::protocol::Variant;
use serde::{Deserialize, Serialize};
use serde_bare::Uint;
use std::marker::PhantomData;
pub trait DataTag: Into<u64> + TryFrom<u64> + ToString {
fn with_variant(self, data: Variant) -> TaggedData<Self>
where
Self: Sized,
{
TaggedData::new(self, data)
}
fn with_unsigned<V: Into<u64>>(self, data: V) -> TaggedData<Self>
where
Self: Sized,
{
TaggedData::new(self, Variant::unsigned(data.into()))
}
fn with_signed<V: Into<i64>>(self, data: V) -> TaggedData<Self>
where
Self: Sized,
{
TaggedData::new(self, Variant::signed(data.into()))
}
fn with_bytes<B: AsRef<[u8]>>(self, data: B) -> TaggedData<Self>
where
Self: Sized,
{
TaggedData::new(self, Variant::Bytes(data.as_ref().to_vec()))
}
fn with_str<S: AsRef<str>>(self, data: S) -> TaggedData<Self>
where
Self: Sized,
{
TaggedData::new(self, Variant::String(data.as_ref().to_owned()))
}
#[must_use]
fn debug_data_for_tag(tag: u64, data: &Variant) -> String {
Self::try_from(tag).map_or_else(|_| format!("{data:?}"), |tag| tag.debug_data(data))
}
#[must_use]
fn debug_data(&self, data: &Variant) -> String {
format!("{data:?}")
}
}
impl<E: DataTag> From<E> for TaggedData<E> {
fn from(value: E) -> Self {
TaggedData::new(value, Variant::Empty)
}
}
#[allow(dead_code)] fn last_component(tn: &'static str) -> &'static str {
tn.rsplit("::").next().unwrap_or(tn)
}
#[derive(
Serialize, Deserialize, PartialEq, Clone, Default, derive_more::Debug, derive_more::Display,
)]
#[display("({}, {})", self.tag_str(), E::debug_data_for_tag(tag.0, data))]
pub struct TaggedData<E: DataTag> {
#[debug("{}::{}", last_component(std::any::type_name::<E>()),
E::try_from(tag.0).map_or_else(|_| format!("UNKNOWN_{}", tag.0), |f| f.to_string()))]
tag: Uint,
#[debug("{}", E::debug_data_for_tag(tag.0, data))]
pub data: Variant,
#[debug(ignore)]
#[serde(skip)]
phantom: PhantomData<E>,
}
impl<E: DataTag> TaggedData<E> {
#[must_use]
pub fn new(option: E, data: Variant) -> Self {
Self {
tag: Uint(option.into()),
data,
phantom: PhantomData,
}
}
#[must_use]
pub fn tag(&self) -> Option<E>
where
E: TryFrom<u64>,
{
E::try_from(self.tag.0).ok()
}
#[must_use]
pub fn tag_raw(&self) -> u64 {
self.tag.0
}
#[must_use]
pub fn tag_str(&self) -> String {
E::try_from(self.tag.0)
.map_or_else(|_| format!("UNKNOWN_{}", self.tag.0), |f| f.to_string())
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
pub(crate) fn new_raw(opt: u64) -> Self {
Self {
tag: Uint(opt),
data: Variant::Empty,
phantom: PhantomData,
}
}
}
pub trait FindTag<E>: std::iter::IntoIterator
where
E: DataTag,
u64: From<E>,
{
fn find_tag(&self, tag: E) -> Option<&Variant>;
}
impl<E> FindTag<E> for Vec<TaggedData<E>>
where
E: DataTag,
u64: From<E>,
{
fn find_tag(&self, tag: E) -> Option<&Variant> {
let wanted = u64::from(tag);
self.iter()
.find(|item| item.tag_raw() == wanted)
.map(|item| &item.data)
}
}
pub(crate) fn display_vec_td<E: DataTag>(v: &Vec<TaggedData<E>>) -> String {
if v.is_empty() {
return "[]".into();
}
let mut s = String::new();
s.push('[');
let mut first = true;
for it in v {
if !first {
s.push_str(", ");
}
first = false;
s.push_str(&it.tag_str());
s.push(':');
s.push_str(&E::debug_data_for_tag(it.tag_raw(), &it.data));
}
s.push(']');
s
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use int_enum::IntEnum;
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::protocol::Variant;
use super::{DataTag, TaggedData};
#[derive(strum_macros::Display, Debug, IntEnum, PartialEq)]
#[repr(u64)]
enum TestTag {
First = 1,
Second = 2,
}
impl DataTag for TestTag {}
#[test]
fn tagged_data() {
let tagged = TestTag::First.with_signed(42);
let s = format!("{tagged:?}");
assert_str_eq!(
s,
"TaggedData { tag: TestTag::First, data: Signed(42), .. }"
);
assert_eq!(tagged.tag(), Some(TestTag::First));
assert_eq!(tagged.tag_raw(), 1);
let s = format!("{tagged}");
assert_str_eq!(s, "(First, Signed(42))");
assert_eq!(tagged.tag(), Some(TestTag::First));
assert_eq!(tagged.tag_raw(), 1);
let tagged: TaggedData<TestTag> = TestTag::Second.into();
let s = format!("{tagged:?}");
assert_str_eq!(s, "TaggedData { tag: TestTag::Second, data: Empty, .. }");
assert_eq!(tagged.tag(), Some(TestTag::Second));
assert_eq!(tagged.tag_raw(), 2);
let tagged: TaggedData<TestTag> = TestTag::Second.into();
let s = format!("{tagged}");
assert_str_eq!(s, "(Second, Empty)");
assert_eq!(tagged.tag(), Some(TestTag::Second));
assert_eq!(tagged.tag_raw(), 2);
}
#[derive(strum_macros::Display, Debug, IntEnum, PartialEq)]
#[repr(u64)]
enum TestTagCustomDebug {
Weasels = 1,
Wombats = 2,
}
impl DataTag for TestTagCustomDebug {
fn debug_data(&self, data: &Variant) -> String {
match self {
TestTagCustomDebug::Weasels => {
format!("{} weasels", data.coerce_unsigned())
}
TestTagCustomDebug::Wombats => format!("{data:?}"),
}
}
}
#[test]
fn tagged_data_custom_debug() {
let tagged = TestTagCustomDebug::Weasels.with_signed(42);
let s = format!("{tagged:?}");
assert_str_eq!(
s,
"TaggedData { tag: TestTagCustomDebug::Weasels, data: 42 weasels, .. }"
);
let s = format!("{tagged}");
assert_str_eq!(s, "(Weasels, 42 weasels)");
}
}