1use thiserror::Error;
2
3#[derive(Error, Debug)]
8#[non_exhaustive]
9pub enum ReputationError {
10 #[error("Invalid DID format: {0}")]
11 InvalidDid(String),
12
13 #[error("Invalid rating: {0} (must be between 1.0 and 5.0)")]
14 InvalidRating(f64),
15
16 #[error("Inconsistent review data: positive ({positive}) + negative ({negative}) != total ({total})")]
17 InconsistentReviews {
18 positive: u64,
19 negative: u64,
20 total: u64,
21 },
22
23 #[error("Validation failed: {0}")]
24 ValidationError(#[from] ValidationError),
25
26 #[error("Calculation error: {0}")]
27 CalculationError(String),
28
29 #[error("Serialization error: {0}")]
30 SerializationError(String),
31}
32
33#[derive(Error, Debug)]
37#[non_exhaustive]
38pub enum ValidationError {
39 #[error("Invalid DID format: {0}")]
40 InvalidDid(String),
41
42 #[error("Date cannot be in the future: {0}")]
43 FutureDate(String),
44
45 #[error("Invalid rating value: {0}")]
46 InvalidRating(f64),
47
48 #[error("Review counts are inconsistent")]
49 InconsistentReviews,
50
51 #[error("Invalid MCP level: {0} (must be 0-3)")]
52 InvalidMcpLevel(u8),
53
54 #[error("Invalid field value: {field} = {value}")]
55 InvalidField {
56 field: String,
57 value: String,
58 },
59}
60
61pub type Result<T> = std::result::Result<T, ReputationError>;
63
64#[derive(Error, Debug)]
66#[non_exhaustive]
67pub enum BuilderError {
68 #[error("Missing required field: {0}")]
69 MissingField(String),
70
71 #[error("Invalid configuration: {0}")]
72 InvalidConfig(String),
73
74 #[error("Validation failed during build")]
75 ValidationFailed(#[from] ValidationError),
76}
77
78#[derive(Error, Debug)]
80#[non_exhaustive]
81pub enum CalculationError {
82 #[error("Score calculation resulted in NaN")]
83 NaNResult,
84
85 #[error("Score out of bounds: {0} (must be 0-100)")]
86 ScoreOutOfBounds(f64),
87
88 #[error("Confidence out of bounds: {0} (must be 0-1)")]
89 ConfidenceOutOfBounds(f64),
90
91 #[error("Invalid confidence target: {0} (must be 0-1)")]
92 InvalidConfidence(f64),
93}
94
95impl From<CalculationError> for ReputationError {
97 fn from(err: CalculationError) -> Self {
98 ReputationError::CalculationError(err.to_string())
99 }
100}
101
102impl From<BuilderError> for ReputationError {
104 fn from(err: BuilderError) -> Self {
105 match err {
106 BuilderError::ValidationFailed(ve) => ReputationError::ValidationError(ve),
107 _ => ReputationError::CalculationError(err.to_string()),
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_reputation_error_display() {
118 let error = ReputationError::InvalidDid("bad-did".to_string());
119 assert_eq!(error.to_string(), "Invalid DID format: bad-did");
120
121 let error = ReputationError::InvalidRating(6.0);
122 assert_eq!(error.to_string(), "Invalid rating: 6 (must be between 1.0 and 5.0)");
123
124 let error = ReputationError::InconsistentReviews {
125 positive: 10,
126 negative: 5,
127 total: 20,
128 };
129 assert_eq!(
130 error.to_string(),
131 "Inconsistent review data: positive (10) + negative (5) != total (20)"
132 );
133 }
134
135 #[test]
136 fn test_validation_error_display() {
137 let error = ValidationError::InvalidDid("bad-did".to_string());
138 assert_eq!(error.to_string(), "Invalid DID format: bad-did");
139
140 let error = ValidationError::FutureDate("2030-01-01".to_string());
141 assert_eq!(error.to_string(), "Date cannot be in the future: 2030-01-01");
142
143 let error = ValidationError::InvalidMcpLevel(5);
144 assert_eq!(error.to_string(), "Invalid MCP level: 5 (must be 0-3)");
145
146 let error = ValidationError::InvalidField {
147 field: "total_interactions".to_string(),
148 value: "-10".to_string(),
149 };
150 assert_eq!(error.to_string(), "Invalid field value: total_interactions = -10");
151 }
152
153 #[test]
154 fn test_builder_error_display() {
155 let error = BuilderError::MissingField("did".to_string());
156 assert_eq!(error.to_string(), "Missing required field: did");
157
158 let error = BuilderError::InvalidConfig("confidence_k must be positive".to_string());
159 assert_eq!(error.to_string(), "Invalid configuration: confidence_k must be positive");
160 }
161
162 #[test]
163 fn test_calculation_error_display() {
164 let error = CalculationError::NaNResult;
165 assert_eq!(error.to_string(), "Score calculation resulted in NaN");
166
167 let error = CalculationError::ScoreOutOfBounds(150.0);
168 assert_eq!(error.to_string(), "Score out of bounds: 150 (must be 0-100)");
169
170 let error = CalculationError::ConfidenceOutOfBounds(1.5);
171 assert_eq!(error.to_string(), "Confidence out of bounds: 1.5 (must be 0-1)");
172 }
173
174 #[test]
175 fn test_error_conversions() {
176 let validation_err = ValidationError::InvalidRating(10.0);
178 let reputation_err: ReputationError = validation_err.into();
179 match reputation_err {
180 ReputationError::ValidationError(ve) => {
181 assert!(matches!(ve, ValidationError::InvalidRating(10.0)));
182 }
183 _ => panic!("Expected ValidationError variant"),
184 }
185
186 let calc_err = CalculationError::NaNResult;
188 let reputation_err: ReputationError = calc_err.into();
189 match reputation_err {
190 ReputationError::CalculationError(msg) => {
191 assert_eq!(msg, "Score calculation resulted in NaN");
192 }
193 _ => panic!("Expected CalculationError variant"),
194 }
195
196 let builder_err = BuilderError::MissingField("test".to_string());
198 let reputation_err: ReputationError = builder_err.into();
199 match reputation_err {
200 ReputationError::CalculationError(msg) => {
201 assert_eq!(msg, "Missing required field: test");
202 }
203 _ => panic!("Expected CalculationError variant"),
204 }
205 }
206
207 #[test]
208 fn test_error_downcast() {
209 use std::error::Error;
210
211 let error = ReputationError::InvalidRating(10.0);
212 assert!(error.source().is_none());
213
214 let validation_err = ValidationError::InvalidRating(10.0);
215 let error = ReputationError::ValidationError(validation_err);
216 assert!(error.source().is_some());
217 }
218
219 #[test]
220 fn test_error_debug_format() {
221 let error = ReputationError::InvalidDid("test".to_string());
222 let debug_str = format!("{:?}", error);
223 assert!(debug_str.contains("InvalidDid"));
224 assert!(debug_str.contains("test"));
225 }
226
227 #[test]
228 fn test_unicode_in_error_messages() {
229 let error = ReputationError::InvalidDid("did:测试:123".to_string());
230 assert_eq!(error.to_string(), "Invalid DID format: did:测试:123");
231
232 let error = ValidationError::InvalidField {
233 field: "フィールド".to_string(),
234 value: "値".to_string(),
235 };
236 assert_eq!(error.to_string(), "Invalid field value: フィールド = 値");
237 }
238
239 #[test]
240 fn test_very_long_error_messages() {
241 let long_did = "did:".to_string() + &"x".repeat(1000);
242 let error = ReputationError::InvalidDid(long_did.clone());
243 assert!(error.to_string().contains(&long_did));
244 }
245
246 #[test]
247 fn test_nested_error_context() {
248 let validation_err = ValidationError::InvalidMcpLevel(10);
249 let builder_err = BuilderError::ValidationFailed(validation_err);
250 let reputation_err: ReputationError = builder_err.into();
251
252 match reputation_err {
253 ReputationError::ValidationError(ve) => {
254 assert!(matches!(ve, ValidationError::InvalidMcpLevel(10)));
255 }
256 _ => panic!("Expected ValidationError variant"),
257 }
258 }
259}