use serde::{Deserialize, Serialize};
pub const SUBNET_TAG_PREFIX: &str = "subnet:";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SubnetId(pub(crate) [u8; 16]);
impl SubnetId {
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
pub fn from_tag(tag: &str) -> Option<Self> {
let hex_part = tag.strip_prefix(SUBNET_TAG_PREFIX)?;
let mut out = [0u8; 16];
hex::decode_to_slice(hex_part, &mut out).ok()?;
Some(Self(out))
}
pub fn to_tag(self) -> String {
format!("{SUBNET_TAG_PREFIX}{}", hex::encode(self.0))
}
}
impl std::fmt::Display for SubnetId {
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 = SubnetId([
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x0F, 0xED, 0xCB, 0xA9, 0x87, 0x65,
0x43, 0x21,
]);
let tag = original.to_tag();
assert_eq!(tag, "subnet:123456789abcdef00fedcba987654321");
let decoded = SubnetId::from_tag(&tag).expect("round trip");
assert_eq!(decoded, original);
}
#[test]
fn parse_rejects_missing_prefix() {
assert!(SubnetId::from_tag("123456789abcdef00fedcba987654321").is_none());
}
#[test]
fn parse_rejects_wrong_length() {
assert!(SubnetId::from_tag("subnet:123456789abcdef00fedcba98765432").is_none());
assert!(SubnetId::from_tag("subnet:123456789abcdef00fedcba9876543211").is_none());
}
#[test]
fn parse_rejects_non_hex_chars() {
assert!(SubnetId::from_tag("subnet:gg3456789abcdef00fedcba987654321").is_none());
assert!(SubnetId::from_tag("subnet: ").is_none());
}
#[test]
fn display_matches_tag_hex() {
let s = SubnetId([0xAB; 16]);
let displayed = format!("{}", s);
assert_eq!(displayed, "abababababababababababababababab");
assert_eq!(s.to_tag(), format!("{}{}", SUBNET_TAG_PREFIX, displayed));
}
#[test]
fn serde_round_trip_postcard() {
let s = SubnetId([0xCC; 16]);
let bytes = postcard::to_allocvec(&s).unwrap();
let decoded: SubnetId = postcard::from_bytes(&bytes).unwrap();
assert_eq!(decoded, s);
}
}