use serde::{Deserialize, Serialize};
pub const GROUP_TAG_PREFIX: &str = "group:";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GroupId(pub(crate) [u8; 32]);
impl GroupId {
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn from_tag(tag: &str) -> Option<Self> {
let hex_part = tag.strip_prefix(GROUP_TAG_PREFIX)?;
let mut out = [0u8; 32];
hex::decode_to_slice(hex_part, &mut out).ok()?;
Some(Self(out))
}
pub fn to_tag(self) -> String {
format!("{GROUP_TAG_PREFIX}{}", hex::encode(self.0))
}
}
impl std::fmt::Display for GroupId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_via_tag_form() {
let original = GroupId([0x5A; 32]);
let tag = original.to_tag();
assert_eq!(
tag,
"group:5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a"
);
let decoded = GroupId::from_tag(&tag).expect("round trip");
assert_eq!(decoded, original);
}
#[test]
fn parse_rejects_missing_prefix() {
let no_prefix = "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a";
assert!(GroupId::from_tag(no_prefix).is_none());
}
#[test]
fn parse_rejects_wrong_length() {
let short = "group:5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5";
assert!(GroupId::from_tag(short).is_none());
let long = "group:5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5aa";
assert!(GroupId::from_tag(long).is_none());
}
#[test]
fn parse_rejects_non_hex_chars() {
let bad = "group:zz5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a";
assert!(GroupId::from_tag(bad).is_none());
}
#[test]
fn distinct_groups_differ() {
let a = GroupId([0x11; 32]);
let b = GroupId([0x22; 32]);
assert_ne!(a, b);
}
#[test]
fn serde_round_trip_postcard() {
let g = GroupId([0xAA; 32]);
let bytes = postcard::to_allocvec(&g).unwrap();
let decoded: GroupId = postcard::from_bytes(&bytes).unwrap();
assert_eq!(decoded, g);
}
}