use crate::AgentData;
use chrono::{DateTime, Utc};
#[derive(Debug, Clone)]
pub struct AgentDataBuilder {
did: String,
created_at: DateTime<Utc>,
mcp_level: Option<u8>,
identity_verified: bool,
security_audit_passed: bool,
open_source: bool,
total_interactions: u32,
total_reviews: u32,
average_rating: Option<f64>,
positive_reviews: u32,
negative_reviews: u32,
}
impl AgentDataBuilder {
pub fn new(did: impl Into<String>) -> Self {
Self {
did: did.into(),
created_at: Utc::now(),
mcp_level: None,
identity_verified: false,
security_audit_passed: false,
open_source: false,
total_interactions: 0,
total_reviews: 0,
average_rating: None,
positive_reviews: 0,
negative_reviews: 0,
}
}
pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
self.created_at = created_at;
self
}
pub fn mcp_level(mut self, level: u8) -> Self {
self.mcp_level = Some(level);
self
}
pub fn identity_verified(mut self, verified: bool) -> Self {
self.identity_verified = verified;
self
}
pub fn security_audit_passed(mut self, passed: bool) -> Self {
self.security_audit_passed = passed;
self
}
pub fn open_source(mut self, is_open_source: bool) -> Self {
self.open_source = is_open_source;
self
}
pub fn total_interactions(mut self, count: u32) -> Self {
self.total_interactions = count;
self
}
pub fn total_reviews(mut self, count: u32) -> Self {
self.total_reviews = count;
self
}
pub fn average_rating(mut self, rating: f64) -> Self {
self.average_rating = Some(rating);
self
}
pub fn positive_reviews(mut self, count: u32) -> Self {
self.positive_reviews = count;
self
}
pub fn negative_reviews(mut self, count: u32) -> Self {
self.negative_reviews = count;
self
}
pub fn with_reviews(mut self, total: u32, average_rating: f64) -> Self {
self.total_reviews = total;
self.average_rating = Some(average_rating);
if total > 0 {
let positive_ratio = (average_rating.max(1.0).min(5.0) - 1.0) / 4.0;
self.positive_reviews = (total as f64 * positive_ratio).round() as u32;
self.negative_reviews = total.saturating_sub(self.positive_reviews);
} else {
self.positive_reviews = 0;
self.negative_reviews = 0;
}
self
}
pub fn build(self) -> Result<AgentData, BuilderError> {
if self.did.is_empty() {
return Err(BuilderError::InvalidField("DID cannot be empty".to_string()));
}
if !self.did.starts_with("did:") {
return Err(BuilderError::InvalidField("DID must start with 'did:' prefix".to_string()));
}
if self.did.contains("..") || self.did.contains("//") || self.did.contains('\'') ||
self.did.contains('"') || self.did.contains(';') || self.did.contains("--") ||
self.did.contains('\n') || self.did.contains('\t') || self.did.contains('<') ||
self.did.contains('>') || self.did.contains('\0') || self.did.contains('\r') ||
self.did.contains("javascript:") {
return Err(BuilderError::InvalidField("DID contains invalid format".to_string()));
}
if self.did.len() > 1000 {
return Err(BuilderError::InvalidField("DID exceeds maximum length".to_string()));
}
let parts: Vec<&str> = self.did.split(':').collect();
if parts.len() < 3 || parts[1].is_empty() || parts[2].is_empty() {
return Err(BuilderError::InvalidField("DID must have format 'did:method:id'".to_string()));
}
if self.created_at > Utc::now() {
return Err(BuilderError::InvalidField("Creation date cannot be in the future".to_string()));
}
let total_reviews = if self.total_reviews == 0 && (self.positive_reviews > 0 || self.negative_reviews > 0) {
self.positive_reviews + self.negative_reviews
} else {
self.total_reviews
};
if total_reviews > 0 && self.positive_reviews + self.negative_reviews > 0 {
if total_reviews != self.positive_reviews + self.negative_reviews {
return Err(BuilderError::InvalidField(
format!(
"Total reviews ({}) must equal positive ({}) + negative ({}) reviews",
total_reviews, self.positive_reviews, self.negative_reviews
)
));
}
}
if total_reviews > self.total_interactions {
return Err(BuilderError::InvalidField(
format!(
"Total reviews ({}) cannot exceed total interactions ({})",
total_reviews, self.total_interactions
)
));
}
let average_rating = match self.average_rating {
Some(rating) => {
if rating.is_nan() || rating.is_infinite() {
return Err(BuilderError::InvalidField(
format!("Average rating must be a valid number, got {}", rating)
));
}
if rating < 1.0 || rating > 5.0 {
return Err(BuilderError::InvalidField(
format!("Average rating must be between 1.0 and 5.0, got {}", rating)
));
}
Some(rating)
}
None => {
if total_reviews > 0 && (self.positive_reviews > 0 || self.negative_reviews > 0) {
let positive_ratio = self.positive_reviews as f64 / total_reviews as f64;
Some(1.0 + (positive_ratio * 4.0))
} else {
None
}
}
};
if let Some(level) = self.mcp_level {
if level > 3 {
return Err(BuilderError::InvalidField(
format!("MCP level must be between 0 and 3, got {}", level)
));
}
}
Ok(AgentData {
did: self.did,
created_at: self.created_at,
mcp_level: self.mcp_level,
identity_verified: self.identity_verified,
security_audit_passed: self.security_audit_passed,
open_source: self.open_source,
total_interactions: self.total_interactions,
total_reviews,
average_rating,
positive_reviews: self.positive_reviews,
negative_reviews: self.negative_reviews,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BuilderError {
InvalidField(String),
}
impl std::fmt::Display for BuilderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuilderError::InvalidField(msg) => write!(f, "Invalid field: {}", msg),
}
}
}
impl std::error::Error for BuilderError {}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Duration;
#[test]
fn test_builder_minimal() {
let agent = AgentDataBuilder::new("did:example:123")
.build()
.unwrap();
assert_eq!(agent.did, "did:example:123");
assert_eq!(agent.mcp_level, None);
assert!(!agent.identity_verified);
assert!(!agent.security_audit_passed);
assert!(!agent.open_source);
assert_eq!(agent.total_interactions, 0);
assert_eq!(agent.total_reviews, 0);
assert_eq!(agent.average_rating, None);
assert_eq!(agent.positive_reviews, 0);
assert_eq!(agent.negative_reviews, 0);
}
#[test]
fn test_builder_with_all_fields() {
let created_at = Utc::now() - Duration::days(30);
let agent = AgentDataBuilder::new("did:example:456")
.created_at(created_at)
.mcp_level(3)
.identity_verified(true)
.security_audit_passed(true)
.open_source(true)
.total_interactions(1000)
.total_reviews(100)
.average_rating(4.2)
.positive_reviews(80)
.negative_reviews(20)
.build()
.unwrap();
assert_eq!(agent.did, "did:example:456");
assert_eq!(agent.created_at, created_at);
assert_eq!(agent.mcp_level, Some(3));
assert!(agent.identity_verified);
assert!(agent.security_audit_passed);
assert!(agent.open_source);
assert_eq!(agent.total_interactions, 1000);
assert_eq!(agent.total_reviews, 100);
assert_eq!(agent.average_rating, Some(4.2));
assert_eq!(agent.positive_reviews, 80);
assert_eq!(agent.negative_reviews, 20);
}
#[test]
fn test_with_reviews_helper() {
let agent = AgentDataBuilder::new("did:example:789")
.total_interactions(150)
.with_reviews(100, 4.5)
.build()
.unwrap();
assert_eq!(agent.total_reviews, 100);
assert_eq!(agent.average_rating, Some(4.5));
assert_eq!(agent.positive_reviews, 88); assert_eq!(agent.negative_reviews, 12);
}
#[test]
fn test_with_reviews_edge_cases() {
let agent = AgentDataBuilder::new("did:example:1")
.total_interactions(60)
.with_reviews(50, 5.0)
.build()
.unwrap();
assert_eq!(agent.positive_reviews, 50);
assert_eq!(agent.negative_reviews, 0);
let agent = AgentDataBuilder::new("did:example:2")
.total_interactions(60)
.with_reviews(50, 1.0)
.build()
.unwrap();
assert_eq!(agent.positive_reviews, 0);
assert_eq!(agent.negative_reviews, 50);
let result = AgentDataBuilder::new("did:example:3")
.with_reviews(100, 6.0)
.build();
assert!(result.is_err());
let result = AgentDataBuilder::new("did:example:4")
.with_reviews(100, 0.5)
.build();
assert!(result.is_err());
}
#[test]
fn test_validation_empty_did() {
let result = AgentDataBuilder::new("")
.build();
assert!(result.is_err());
match result.unwrap_err() {
BuilderError::InvalidField(msg) => assert!(msg.contains("DID cannot be empty")),
}
}
#[test]
fn test_validation_future_date() {
let result = AgentDataBuilder::new("did:example:123")
.created_at(Utc::now() + Duration::days(1))
.build();
assert!(result.is_err());
match result.unwrap_err() {
BuilderError::InvalidField(msg) => assert!(msg.contains("future")),
}
}
#[test]
fn test_validation_review_mismatch() {
let result = AgentDataBuilder::new("did:example:123")
.total_reviews(100)
.positive_reviews(60)
.negative_reviews(50) .build();
assert!(result.is_err());
match result.unwrap_err() {
BuilderError::InvalidField(msg) => assert!(msg.contains("must equal")),
}
}
#[test]
fn test_validation_invalid_rating() {
let result = AgentDataBuilder::new("did:example:123")
.average_rating(5.5)
.build();
assert!(result.is_err());
match result.unwrap_err() {
BuilderError::InvalidField(msg) => assert!(msg.contains("between 1.0 and 5.0")),
}
}
#[test]
fn test_validation_invalid_mcp_level() {
let result = AgentDataBuilder::new("did:example:123")
.mcp_level(10)
.build();
assert!(result.is_err());
match result.unwrap_err() {
BuilderError::InvalidField(msg) => assert!(msg.contains("MCP level")),
}
}
#[test]
fn test_auto_calculate_total_reviews() {
let agent = AgentDataBuilder::new("did:example:123")
.total_interactions(120)
.positive_reviews(75)
.negative_reviews(25)
.build()
.unwrap();
assert_eq!(agent.total_reviews, 100);
}
#[test]
fn test_auto_calculate_average_rating() {
let agent = AgentDataBuilder::new("did:example:123")
.total_interactions(120)
.positive_reviews(80)
.negative_reviews(20)
.build()
.unwrap();
assert_eq!(agent.average_rating, Some(4.2));
}
#[test]
fn test_builder_is_cloneable() {
let builder = AgentDataBuilder::new("did:example:123")
.mcp_level(2)
.identity_verified(true);
let builder2 = builder.clone();
let agent1 = builder.build().unwrap();
let agent2 = builder2.build().unwrap();
assert_eq!(agent1.did, agent2.did);
assert_eq!(agent1.mcp_level, agent2.mcp_level);
assert_eq!(agent1.identity_verified, agent2.identity_verified);
}
#[test]
fn test_method_chaining() {
let _agent = AgentDataBuilder::new("did:example:123")
.created_at(Utc::now())
.mcp_level(2)
.identity_verified(true)
.security_audit_passed(true)
.open_source(true)
.total_interactions(1000)
.with_reviews(100, 4.5)
.build()
.unwrap();
}
#[test]
fn test_builder_error_display() {
let error = BuilderError::InvalidField("test error".to_string());
assert_eq!(error.to_string(), "Invalid field: test error");
let _: &dyn std::error::Error = &error;
}
}