id.rs (4958B)
1 use crate::Error as TransportError; 2 use alloc::string::{String, ToString}; 3 use core::{fmt, str::FromStr}; 4 use radroots_protocol::capability::v1::{ 5 Error as ProtocolError, MAX_TRANSPORT_KIND_BYTES, TransportKind as ProtocolTransportKind, 6 }; 7 8 /// Maximum encoded length of a transport identity. 9 pub const TRANSPORT_ID_MAX_BYTES: usize = MAX_TRANSPORT_KIND_BYTES; 10 11 /// Validated, extensible transport identity. 12 /// 13 /// Identities contain 1-64 canonical lowercase ASCII bytes. They begin and 14 /// end with a letter or digit and may use single `-` separators internally. 15 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 16 pub struct TransportId(ProtocolTransportKind); 17 18 impl TransportId { 19 /// Process-local transport. 20 pub const LOCAL: Self = Self(ProtocolTransportKind::LOCAL); 21 /// Nostr relay transport. 22 pub const NOSTR: Self = Self(ProtocolTransportKind::NOSTR); 23 /// Reticulum mesh transport. 24 pub const RETICULUM: Self = Self(ProtocolTransportKind::RETICULUM); 25 /// Daemon-mediated transport. 26 pub const RADROOTSD: Self = Self(ProtocolTransportKind::RADROOTSD); 27 28 /// Parses an exact canonical identity. 29 pub fn parse(value: impl AsRef<str>) -> Result<Self, TransportError> { 30 ProtocolTransportKind::parse(value.as_ref()) 31 .map(Self) 32 .map_err(map_protocol_error) 33 } 34 35 /// Parses an exact canonical identity. 36 pub fn parse_canonical(value: impl AsRef<str>) -> Result<Self, TransportError> { 37 Self::parse(value) 38 } 39 40 /// Returns the canonical identity text. 41 pub fn as_str(&self) -> &str { 42 self.0.as_str() 43 } 44 45 /// Returns an owned canonical identity label. 46 pub fn canonical_label(&self) -> String { 47 self.as_str().to_string() 48 } 49 } 50 51 fn map_protocol_error(error: ProtocolError) -> TransportError { 52 match error { 53 ProtocolError::EmptyTransportKind => TransportError::EmptyTransportKind, 54 ProtocolError::InvalidTransportKind { .. } => TransportError::InvalidTransportKind, 55 _ => TransportError::InvalidTransportKind, 56 } 57 } 58 59 impl AsRef<str> for TransportId { 60 fn as_ref(&self) -> &str { 61 self.as_str() 62 } 63 } 64 65 impl fmt::Display for TransportId { 66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 67 formatter.write_str(self.as_str()) 68 } 69 } 70 71 impl FromStr for TransportId { 72 type Err = TransportError; 73 74 fn from_str(value: &str) -> Result<Self, Self::Err> { 75 Self::parse(value) 76 } 77 } 78 79 impl TryFrom<&str> for TransportId { 80 type Error = TransportError; 81 82 fn try_from(value: &str) -> Result<Self, Self::Error> { 83 Self::parse(value) 84 } 85 } 86 87 impl TryFrom<String> for TransportId { 88 type Error = TransportError; 89 90 fn try_from(value: String) -> Result<Self, Self::Error> { 91 Self::parse(value) 92 } 93 } 94 95 impl From<ProtocolTransportKind> for TransportId { 96 fn from(value: ProtocolTransportKind) -> Self { 97 Self(value) 98 } 99 } 100 101 impl From<TransportId> for ProtocolTransportKind { 102 fn from(value: TransportId) -> Self { 103 value.0 104 } 105 } 106 107 #[cfg(feature = "serde")] 108 impl serde::Serialize for TransportId { 109 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 110 where 111 S: serde::Serializer, 112 { 113 self.0.serialize(serializer) 114 } 115 } 116 117 #[cfg(feature = "serde")] 118 impl<'de> serde::Deserialize<'de> for TransportId { 119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 120 where 121 D: serde::Deserializer<'de>, 122 { 123 <ProtocolTransportKind as serde::Deserialize>::deserialize(deserializer).map(Self) 124 } 125 } 126 127 #[cfg(test)] 128 mod tests { 129 use super::*; 130 131 #[test] 132 fn transport_ids_cover_conversion_and_validation_surfaces() { 133 let id = TransportId::parse_canonical("custom-transport").unwrap(); 134 assert_eq!(id.as_str(), "custom-transport"); 135 assert_eq!(id.as_ref(), "custom-transport"); 136 assert_eq!(id.to_string(), "custom-transport"); 137 assert_eq!(id.canonical_label(), "custom-transport"); 138 assert_eq!(TransportId::from_str("custom-transport").unwrap(), id); 139 assert_eq!(TransportId::try_from("custom-transport").unwrap(), id); 140 assert_eq!( 141 TransportId::try_from(String::from("custom-transport")).unwrap(), 142 id 143 ); 144 let protocol_id: ProtocolTransportKind = id.into(); 145 assert_eq!(TransportId::from(protocol_id), id); 146 assert_eq!( 147 TransportId::parse(""), 148 Err(TransportError::EmptyTransportKind) 149 ); 150 assert_eq!( 151 TransportId::parse("Invalid"), 152 Err(TransportError::InvalidTransportKind) 153 ); 154 155 #[cfg(feature = "serde")] 156 { 157 let encoded = serde_json::to_string(&id).unwrap(); 158 assert_eq!(serde_json::from_str::<TransportId>(&encoded).unwrap(), id); 159 assert!(serde_json::from_str::<TransportId>("\"Invalid\"").is_err()); 160 } 161 } 162 }