reputation_core/
error.rs

1use thiserror::Error;
2
3/// Main error type for the reputation system.
4/// 
5/// This enum represents all possible errors that can occur during
6/// reputation score calculation and validation.
7#[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/// Validation-specific errors.
34/// 
35/// These errors occur when validating agent data or configuration values.
36#[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
61// Type alias for convenience
62pub type Result<T> = std::result::Result<T, ReputationError>;
63
64/// Errors that can occur when building or configuring calculators.
65#[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/// Errors specific to reputation score calculations.
79#[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
95// Implement conversion from CalculationError to ReputationError
96impl From<CalculationError> for ReputationError {
97    fn from(err: CalculationError) -> Self {
98        ReputationError::CalculationError(err.to_string())
99    }
100}
101
102// Implement conversion from BuilderError to ReputationError
103impl 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        // Test ValidationError -> ReputationError
177        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        // Test CalculationError -> ReputationError
187        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        // Test BuilderError -> ReputationError
197        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}