1use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10const PREFIX: &str = "did:keri:";
11
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
25#[serde(try_from = "String", into = "String")]
26pub struct KeriDid(String);
27
28impl KeriDid {
29 pub fn parse(s: &str) -> Result<Self, KeriDidError> {
39 let keri_prefix = s.strip_prefix(PREFIX).ok_or(KeriDidError::MissingPrefix)?;
40 if keri_prefix.is_empty() {
41 return Err(KeriDidError::EmptyPrefix);
42 }
43 Ok(Self(s.to_string()))
44 }
45
46 pub fn from_prefix(prefix: &str) -> Result<Self, KeriDidError> {
57 if prefix.is_empty() {
58 return Err(KeriDidError::EmptyPrefix);
59 }
60 Ok(Self(format!("{}{}", PREFIX, prefix)))
61 }
62
63 pub fn prefix(&self) -> &str {
65 &self.0[PREFIX.len()..]
67 }
68
69 pub fn as_str(&self) -> &str {
71 &self.0
72 }
73}
74
75impl fmt::Display for KeriDid {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str(&self.0)
78 }
79}
80
81impl AsRef<str> for KeriDid {
82 fn as_ref(&self) -> &str {
83 &self.0
84 }
85}
86
87impl From<KeriDid> for String {
88 fn from(did: KeriDid) -> Self {
89 did.0
90 }
91}
92
93impl TryFrom<String> for KeriDid {
94 type Error = KeriDidError;
95
96 fn try_from(s: String) -> Result<Self, Self::Error> {
97 let keri_prefix = s.strip_prefix(PREFIX).ok_or(KeriDidError::MissingPrefix)?;
98 if keri_prefix.is_empty() {
99 return Err(KeriDidError::EmptyPrefix);
100 }
101 Ok(Self(s))
102 }
103}
104
105impl TryFrom<&str> for KeriDid {
106 type Error = KeriDidError;
107
108 fn try_from(s: &str) -> Result<Self, Self::Error> {
109 Self::parse(s)
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
115pub enum KeriDidError {
116 #[error("not a did:keri: identifier")]
118 MissingPrefix,
119
120 #[error("did:keri: prefix is empty")]
122 EmptyPrefix,
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn parse_valid() {
131 let did = KeriDid::parse("did:keri:EXq5abc123").unwrap();
132 assert_eq!(did.prefix(), "EXq5abc123");
133 assert_eq!(did.as_str(), "did:keri:EXq5abc123");
134 assert_eq!(did.to_string(), "did:keri:EXq5abc123");
135 }
136
137 #[test]
138 fn from_prefix_valid() {
139 let did = KeriDid::from_prefix("EXq5abc123").unwrap();
140 assert_eq!(did.as_str(), "did:keri:EXq5abc123");
141 assert_eq!(did.prefix(), "EXq5abc123");
142 }
143
144 #[test]
145 fn rejects_non_keri() {
146 assert_eq!(
147 KeriDid::parse("did:key:z6Mk123"),
148 Err(KeriDidError::MissingPrefix)
149 );
150 }
151
152 #[test]
153 fn rejects_empty_prefix() {
154 assert_eq!(KeriDid::parse("did:keri:"), Err(KeriDidError::EmptyPrefix));
155 }
156
157 #[test]
158 fn rejects_missing_scheme() {
159 assert_eq!(KeriDid::parse("EXq5abc"), Err(KeriDidError::MissingPrefix));
160 }
161
162 #[test]
163 fn from_prefix_rejects_empty() {
164 assert_eq!(KeriDid::from_prefix(""), Err(KeriDidError::EmptyPrefix));
165 }
166
167 #[test]
168 fn try_from_string() {
169 let did: KeriDid = "did:keri:EXq5".to_string().try_into().unwrap();
170 assert_eq!(did.prefix(), "EXq5");
171 }
172
173 #[test]
174 fn into_string() {
175 let did = KeriDid::parse("did:keri:EXq5").unwrap();
176 let s: String = did.into();
177 assert_eq!(s, "did:keri:EXq5");
178 }
179
180 #[test]
181 fn serde_roundtrip() {
182 let did = KeriDid::parse("did:keri:EXq5abc").unwrap();
183 let json = serde_json::to_string(&did).unwrap();
184 assert_eq!(json, r#""did:keri:EXq5abc""#);
185 let parsed: KeriDid = serde_json::from_str(&json).unwrap();
186 assert_eq!(parsed, did);
187 }
188
189 #[test]
190 fn serde_rejects_invalid() {
191 let result: Result<KeriDid, _> = serde_json::from_str(r#""did:key:z6Mk""#);
192 assert!(result.is_err());
193 }
194}