1use crate::witness::WitnessQuorum;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct VerificationReport {
14 pub status: VerificationStatus,
16 pub chain: Vec<ChainLink>,
18 pub warnings: Vec<String>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub witness_quorum: Option<WitnessQuorum>,
23}
24
25impl VerificationReport {
26 pub fn is_valid(&self) -> bool {
28 matches!(self.status, VerificationStatus::Valid)
29 }
30
31 pub fn valid(chain: Vec<ChainLink>) -> Self {
33 Self {
34 status: VerificationStatus::Valid,
35 chain,
36 warnings: Vec::new(),
37 witness_quorum: None,
38 }
39 }
40
41 pub fn with_status(status: VerificationStatus, chain: Vec<ChainLink>) -> Self {
43 Self {
44 status,
45 chain,
46 warnings: Vec::new(),
47 witness_quorum: None,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54#[serde(tag = "type")]
55pub enum VerificationStatus {
56 Valid,
58 Expired {
60 at: DateTime<Utc>,
62 },
63 Revoked {
65 at: Option<DateTime<Utc>>,
67 },
68 InvalidSignature {
70 step: usize,
72 },
73 BrokenChain {
75 missing_link: String,
77 },
78 InsufficientWitnesses {
80 required: usize,
82 verified: usize,
84 },
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
89pub struct ChainLink {
90 pub issuer: String,
92 pub subject: String,
94 pub valid: bool,
96 pub error: Option<String>,
98}
99
100impl ChainLink {
101 pub fn valid(issuer: String, subject: String) -> Self {
103 Self {
104 issuer,
105 subject,
106 valid: true,
107 error: None,
108 }
109 }
110
111 pub fn invalid(issuer: String, subject: String, error: String) -> Self {
113 Self {
114 issuer,
115 subject,
116 valid: false,
117 error: Some(error),
118 }
119 }
120}
121
122use std::borrow::Borrow;
127use std::fmt;
128use std::ops::Deref;
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
144#[repr(transparent)]
145pub struct IdentityDID(pub String);
146
147impl IdentityDID {
148 pub fn new<S: Into<String>>(s: S) -> Self {
150 Self(s.into())
151 }
152
153 pub fn new_unchecked(s: String) -> Self {
155 Self(s)
156 }
157
158 pub fn as_str(&self) -> &str {
160 &self.0
161 }
162
163 pub fn into_inner(self) -> String {
165 self.0
166 }
167}
168
169impl fmt::Display for IdentityDID {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.write_str(&self.0)
172 }
173}
174
175impl From<&str> for IdentityDID {
176 fn from(s: &str) -> Self {
177 Self(s.to_string())
178 }
179}
180
181impl From<String> for IdentityDID {
182 fn from(s: String) -> Self {
183 Self(s)
184 }
185}
186
187impl From<IdentityDID> for String {
188 fn from(did: IdentityDID) -> String {
189 did.0
190 }
191}
192
193impl Deref for IdentityDID {
194 type Target = str;
195
196 fn deref(&self) -> &Self::Target {
197 &self.0
198 }
199}
200
201impl AsRef<str> for IdentityDID {
202 fn as_ref(&self) -> &str {
203 &self.0
204 }
205}
206
207impl Borrow<str> for IdentityDID {
208 fn borrow(&self) -> &str {
209 &self.0
210 }
211}
212
213impl PartialEq<str> for IdentityDID {
214 fn eq(&self, other: &str) -> bool {
215 self.0 == other
216 }
217}
218
219impl PartialEq<&str> for IdentityDID {
220 fn eq(&self, other: &&str) -> bool {
221 self.0 == *other
222 }
223}
224
225impl PartialEq<IdentityDID> for str {
226 fn eq(&self, other: &IdentityDID) -> bool {
227 self == other.0
228 }
229}
230
231impl PartialEq<IdentityDID> for &str {
232 fn eq(&self, other: &IdentityDID) -> bool {
233 *self == other.0
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
243pub struct DeviceDID(pub String);
244
245impl DeviceDID {
246 pub fn new<S: Into<String>>(s: S) -> Self {
248 DeviceDID(s.into())
249 }
250
251 pub fn from_ed25519(pubkey: &[u8; 32]) -> Self {
255 let mut prefixed = vec![0xED, 0x01];
256 prefixed.extend_from_slice(pubkey);
257
258 let encoded = bs58::encode(prefixed).into_string();
259 Self(format!("did:key:z{}", encoded))
260 }
261
262 pub fn ref_name(&self) -> String {
265 self.0
266 .chars()
267 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
268 .collect()
269 }
270
271 pub fn matches_sanitized_ref(&self, ref_name: &str) -> bool {
274 self.ref_name() == ref_name
275 }
276
277 pub fn from_sanitized<'a>(
280 sanitized: &str,
281 known_dids: &'a [DeviceDID],
282 ) -> Option<&'a DeviceDID> {
283 known_dids.iter().find(|did| did.ref_name() == sanitized)
284 }
285
286 pub fn as_str(&self) -> &str {
288 &self.0
289 }
290}
291
292impl fmt::Display for DeviceDID {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 self.0.fmt(f)
296 }
297}
298
299impl From<&str> for DeviceDID {
301 fn from(s: &str) -> Self {
302 DeviceDID(s.to_string())
303 }
304}
305
306impl From<String> for DeviceDID {
307 fn from(s: String) -> Self {
308 DeviceDID(s)
309 }
310}
311
312impl Deref for DeviceDID {
314 type Target = str;
315
316 fn deref(&self) -> &Self::Target {
317 &self.0
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::keri::Said;
325
326 #[test]
327 fn report_without_witness_quorum_deserializes() {
328 let json = r#"{
330 "status": {"type": "Valid"},
331 "chain": [],
332 "warnings": []
333 }"#;
334 let report: VerificationReport = serde_json::from_str(json).unwrap();
335 assert!(report.is_valid());
336 assert!(report.witness_quorum.is_none());
337 }
338
339 #[test]
340 fn insufficient_witnesses_serializes_correctly() {
341 let status = VerificationStatus::InsufficientWitnesses {
342 required: 3,
343 verified: 1,
344 };
345 let json = serde_json::to_string(&status).unwrap();
346 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
347 assert_eq!(parsed["type"], "InsufficientWitnesses");
348 assert_eq!(parsed["required"], 3);
349 assert_eq!(parsed["verified"], 1);
350
351 let roundtripped: VerificationStatus = serde_json::from_str(&json).unwrap();
353 assert_eq!(roundtripped, status);
354 }
355
356 #[test]
357 fn report_with_witness_quorum_roundtrips() {
358 use crate::witness::{WitnessQuorum, WitnessReceiptResult};
359
360 let report = VerificationReport {
361 status: VerificationStatus::Valid,
362 chain: vec![],
363 warnings: vec![],
364 witness_quorum: Some(WitnessQuorum {
365 required: 2,
366 verified: 2,
367 receipts: vec![
368 WitnessReceiptResult {
369 witness_id: "did:key:w1".into(),
370 receipt_said: Said::new_unchecked("EReceipt1".into()),
371 verified: true,
372 },
373 WitnessReceiptResult {
374 witness_id: "did:key:w2".into(),
375 receipt_said: Said::new_unchecked("EReceipt2".into()),
376 verified: true,
377 },
378 ],
379 }),
380 };
381
382 let json = serde_json::to_string(&report).unwrap();
383 let parsed: VerificationReport = serde_json::from_str(&json).unwrap();
384 assert_eq!(report, parsed);
385 assert!(parsed.witness_quorum.is_some());
386 assert_eq!(parsed.witness_quorum.unwrap().verified, 2);
387 }
388
389 #[test]
390 fn report_without_witness_quorum_skips_in_json() {
391 let report = VerificationReport::valid(vec![]);
392 let json = serde_json::to_string(&report).unwrap();
393 assert!(!json.contains("witness_quorum"));
395 }
396}