use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ReputationError {
#[error("Invalid DID format: {0}")]
InvalidDid(String),
#[error("Invalid rating: {0} (must be between 1.0 and 5.0)")]
InvalidRating(f64),
#[error("Inconsistent review data: positive ({positive}) + negative ({negative}) != total ({total})")]
InconsistentReviews {
positive: u64,
negative: u64,
total: u64,
},
#[error("Validation failed: {0}")]
ValidationError(#[from] ValidationError),
#[error("Calculation error: {0}")]
CalculationError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ValidationError {
#[error("Invalid DID format: {0}")]
InvalidDid(String),
#[error("Date cannot be in the future: {0}")]
FutureDate(String),
#[error("Invalid rating value: {0}")]
InvalidRating(f64),
#[error("Review counts are inconsistent")]
InconsistentReviews,
#[error("Invalid MCP level: {0} (must be 0-3)")]
InvalidMcpLevel(u8),
#[error("Invalid field value: {field} = {value}")]
InvalidField {
field: String,
value: String,
},
}
pub type Result<T> = std::result::Result<T, ReputationError>;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum BuilderError {
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Validation failed during build")]
ValidationFailed(#[from] ValidationError),
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CalculationError {
#[error("Score calculation resulted in NaN")]
NaNResult,
#[error("Score out of bounds: {0} (must be 0-100)")]
ScoreOutOfBounds(f64),
#[error("Confidence out of bounds: {0} (must be 0-1)")]
ConfidenceOutOfBounds(f64),
#[error("Invalid confidence target: {0} (must be 0-1)")]
InvalidConfidence(f64),
}
impl From<CalculationError> for ReputationError {
fn from(err: CalculationError) -> Self {
ReputationError::CalculationError(err.to_string())
}
}
impl From<BuilderError> for ReputationError {
fn from(err: BuilderError) -> Self {
match err {
BuilderError::ValidationFailed(ve) => ReputationError::ValidationError(ve),
_ => ReputationError::CalculationError(err.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reputation_error_display() {
let error = ReputationError::InvalidDid("bad-did".to_string());
assert_eq!(error.to_string(), "Invalid DID format: bad-did");
let error = ReputationError::InvalidRating(6.0);
assert_eq!(error.to_string(), "Invalid rating: 6 (must be between 1.0 and 5.0)");
let error = ReputationError::InconsistentReviews {
positive: 10,
negative: 5,
total: 20,
};
assert_eq!(
error.to_string(),
"Inconsistent review data: positive (10) + negative (5) != total (20)"
);
}
#[test]
fn test_validation_error_display() {
let error = ValidationError::InvalidDid("bad-did".to_string());
assert_eq!(error.to_string(), "Invalid DID format: bad-did");
let error = ValidationError::FutureDate("2030-01-01".to_string());
assert_eq!(error.to_string(), "Date cannot be in the future: 2030-01-01");
let error = ValidationError::InvalidMcpLevel(5);
assert_eq!(error.to_string(), "Invalid MCP level: 5 (must be 0-3)");
let error = ValidationError::InvalidField {
field: "total_interactions".to_string(),
value: "-10".to_string(),
};
assert_eq!(error.to_string(), "Invalid field value: total_interactions = -10");
}
#[test]
fn test_builder_error_display() {
let error = BuilderError::MissingField("did".to_string());
assert_eq!(error.to_string(), "Missing required field: did");
let error = BuilderError::InvalidConfig("confidence_k must be positive".to_string());
assert_eq!(error.to_string(), "Invalid configuration: confidence_k must be positive");
}
#[test]
fn test_calculation_error_display() {
let error = CalculationError::NaNResult;
assert_eq!(error.to_string(), "Score calculation resulted in NaN");
let error = CalculationError::ScoreOutOfBounds(150.0);
assert_eq!(error.to_string(), "Score out of bounds: 150 (must be 0-100)");
let error = CalculationError::ConfidenceOutOfBounds(1.5);
assert_eq!(error.to_string(), "Confidence out of bounds: 1.5 (must be 0-1)");
}
#[test]
fn test_error_conversions() {
let validation_err = ValidationError::InvalidRating(10.0);
let reputation_err: ReputationError = validation_err.into();
match reputation_err {
ReputationError::ValidationError(ve) => {
assert!(matches!(ve, ValidationError::InvalidRating(10.0)));
}
_ => panic!("Expected ValidationError variant"),
}
let calc_err = CalculationError::NaNResult;
let reputation_err: ReputationError = calc_err.into();
match reputation_err {
ReputationError::CalculationError(msg) => {
assert_eq!(msg, "Score calculation resulted in NaN");
}
_ => panic!("Expected CalculationError variant"),
}
let builder_err = BuilderError::MissingField("test".to_string());
let reputation_err: ReputationError = builder_err.into();
match reputation_err {
ReputationError::CalculationError(msg) => {
assert_eq!(msg, "Missing required field: test");
}
_ => panic!("Expected CalculationError variant"),
}
}
#[test]
fn test_error_downcast() {
use std::error::Error;
let error = ReputationError::InvalidRating(10.0);
assert!(error.source().is_none());
let validation_err = ValidationError::InvalidRating(10.0);
let error = ReputationError::ValidationError(validation_err);
assert!(error.source().is_some());
}
#[test]
fn test_error_debug_format() {
let error = ReputationError::InvalidDid("test".to_string());
let debug_str = format!("{:?}", error);
assert!(debug_str.contains("InvalidDid"));
assert!(debug_str.contains("test"));
}
#[test]
fn test_unicode_in_error_messages() {
let error = ReputationError::InvalidDid("did:测试:123".to_string());
assert_eq!(error.to_string(), "Invalid DID format: did:测试:123");
let error = ValidationError::InvalidField {
field: "フィールド".to_string(),
value: "値".to_string(),
};
assert_eq!(error.to_string(), "Invalid field value: フィールド = 値");
}
#[test]
fn test_very_long_error_messages() {
let long_did = "did:".to_string() + &"x".repeat(1000);
let error = ReputationError::InvalidDid(long_did.clone());
assert!(error.to_string().contains(&long_did));
}
#[test]
fn test_nested_error_context() {
let validation_err = ValidationError::InvalidMcpLevel(10);
let builder_err = BuilderError::ValidationFailed(validation_err);
let reputation_err: ReputationError = builder_err.into();
match reputation_err {
ReputationError::ValidationError(ve) => {
assert!(matches!(ve, ValidationError::InvalidMcpLevel(10)));
}
_ => panic!("Expected ValidationError variant"),
}
}
}