auths_keri/witness/
error.rs1use crate::{Prefix, Said};
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct DuplicityEvidence {
24 pub prefix: Prefix,
26 pub sequence: u128,
28 pub event_a_said: Said,
30 pub event_b_said: Said,
32 pub witness_reports: Vec<WitnessReport>,
34}
35
36impl fmt::Display for DuplicityEvidence {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(
39 f,
40 "Duplicity detected for {} at seq {}: {} vs {}",
41 self.prefix, self.sequence, self.event_a_said, self.event_b_said
42 )
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct WitnessReport {
49 pub witness_id: String,
51 pub observed_said: Said,
53 pub observed_at: Option<String>,
55}
56
57#[derive(Debug, thiserror::Error)]
62#[non_exhaustive]
63pub enum WitnessError {
64 #[error("network error: {0}")]
66 Network(String),
67
68 #[error("duplicity detected: {0}")]
72 Duplicity(DuplicityEvidence),
73
74 #[error("event rejected: {reason}")]
79 Rejected {
80 reason: String,
82 },
83
84 #[error("timeout after {0}ms")]
86 Timeout(u64),
87
88 #[error("invalid receipt signature from witness {witness_id}")]
90 InvalidSignature {
91 witness_id: String,
93 },
94
95 #[error("insufficient receipts: got {got}, need {required}")]
97 InsufficientReceipts {
98 got: usize,
100 required: usize,
102 },
103
104 #[error("receipt SAID mismatch: expected {expected}, got {got}")]
106 SaidMismatch {
107 expected: Said,
109 got: Said,
111 },
112
113 #[error("storage error: {0}")]
115 Storage(String),
116
117 #[error("serialization error: {0}")]
119 Serialization(String),
120}
121
122impl auths_crypto::AuthsErrorInfo for WitnessError {
123 fn error_code(&self) -> &'static str {
124 match self {
125 Self::Network(_) => "AUTHS-E3401",
126 Self::Duplicity(_) => "AUTHS-E3402",
127 Self::Rejected { .. } => "AUTHS-E3403",
128 Self::Timeout(_) => "AUTHS-E3404",
129 Self::InvalidSignature { .. } => "AUTHS-E3405",
130 Self::InsufficientReceipts { .. } => "AUTHS-E3406",
131 Self::SaidMismatch { .. } => "AUTHS-E3407",
132 Self::Storage(_) => "AUTHS-E3408",
133 Self::Serialization(_) => "AUTHS-E3409",
134 }
135 }
136
137 fn suggestion(&self) -> Option<&'static str> {
138 match self {
139 Self::Duplicity(_) => {
140 Some("This identity may be compromised — investigate immediately")
141 }
142 Self::Timeout(_) => Some("Check witness endpoint availability and retry"),
143 Self::InsufficientReceipts { .. } => Some("Ensure enough witnesses are online"),
144 Self::Network(_) => Some("Check your internet connection"),
145 _ => None,
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn duplicity_evidence_display() {
156 let evidence = DuplicityEvidence {
157 prefix: Prefix::new_unchecked("EPrefix123".into()),
158 sequence: 5,
159 event_a_said: Said::new_unchecked("ESAID_A".into()),
160 event_b_said: Said::new_unchecked("ESAID_B".into()),
161 witness_reports: vec![],
162 };
163 let display = format!("{}", evidence);
164 assert!(display.contains("EPrefix123"));
165 assert!(display.contains("5"));
166 assert!(display.contains("ESAID_A"));
167 assert!(display.contains("ESAID_B"));
168 }
169
170 #[test]
171 fn witness_error_variants() {
172 let network_err = WitnessError::Network("connection refused".into());
173 assert!(format!("{}", network_err).contains("network error"));
174
175 let timeout_err = WitnessError::Timeout(5000);
176 assert!(format!("{}", timeout_err).contains("5000ms"));
177
178 let rejected_err = WitnessError::Rejected {
179 reason: "invalid format".into(),
180 };
181 assert!(format!("{}", rejected_err).contains("invalid format"));
182 }
183
184 #[test]
185 fn duplicity_evidence_serialization() {
186 let evidence = DuplicityEvidence {
187 prefix: Prefix::new_unchecked("EPrefix123".into()),
188 sequence: 5,
189 event_a_said: Said::new_unchecked("ESAID_A".into()),
190 event_b_said: Said::new_unchecked("ESAID_B".into()),
191 witness_reports: vec![WitnessReport {
192 witness_id: "did:key:witness1".into(),
193 observed_said: Said::new_unchecked("ESAID_A".into()),
194 observed_at: Some("2024-01-01T00:00:00Z".into()),
195 }],
196 };
197
198 let json = serde_json::to_string(&evidence).unwrap();
199 let parsed: DuplicityEvidence = serde_json::from_str(&json).unwrap();
200 assert_eq!(evidence, parsed);
201 }
202}