use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(tag = "method", content = "id")] pub enum Did { Plc { id: String }, Web { id: String }, Key { id: String }, } impl Did { pub fn method(&self) -> &'static str { match self { Self::Plc { .. } => "plc", Self::Web { .. } => "web", Self::Key { .. } => "key", } } pub fn id(&self) -> &str { match self { Self::Plc { id } | Self::Web { id } | Self::Key { id } => id, } } pub fn as_str(&self) -> String { format!("did:{}:{}", self.method(), self.id()) } } impl std::fmt::Display for Did { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.as_str()) } } impl std::str::FromStr for Did { type Err = anyhow::Error; fn from_str(s: &str) -> anyhow::Result { let s = s.strip_prefix("did:").ok_or_else(|| anyhow::anyhow!("not a did"))?; let (method, rest) = s .split_once(':') .ok_or_else(|| anyhow::anyhow!("malformed did"))?; Ok(match method { "plc" => Self::Plc { id: rest.to_string() }, "web" => Self::Web { id: rest.to_string() }, "key" => Self::Key { id: rest.to_string() }, other => anyhow::bail!("unknown did method: {other}"), }) } } #[cfg(test)] mod tests { use super::*; use std::str::FromStr; #[test] fn roundtrip_did() { let d: Did = "did:plc:abc123def".parse().unwrap(); assert_eq!(d, Did::Plc { id: "abc123def".into() }); assert_eq!(d.as_str(), "did:plc:abc123def"); } #[test] fn rejects_invalid() { assert!("not-a-did".parse::().is_err()); assert!("did:unknown:x".parse::().is_err()); } }