1use crate::AgentData;
2use chrono::{DateTime, Utc};
3
4#[derive(Debug, Clone)]
81pub struct AgentDataBuilder {
82 did: String,
83 created_at: DateTime<Utc>,
84 mcp_level: Option<u8>,
85 identity_verified: bool,
86 security_audit_passed: bool,
87 open_source: bool,
88 total_interactions: u32,
89 total_reviews: u32,
90 average_rating: Option<f64>,
91 positive_reviews: u32,
92 negative_reviews: u32,
93}
94
95impl AgentDataBuilder {
96 pub fn new(did: impl Into<String>) -> Self {
106 Self {
107 did: did.into(),
108 created_at: Utc::now(),
109 mcp_level: None,
110 identity_verified: false,
111 security_audit_passed: false,
112 open_source: false,
113 total_interactions: 0,
114 total_reviews: 0,
115 average_rating: None,
116 positive_reviews: 0,
117 negative_reviews: 0,
118 }
119 }
120
121 pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
123 self.created_at = created_at;
124 self
125 }
126
127 pub fn mcp_level(mut self, level: u8) -> Self {
129 self.mcp_level = Some(level);
130 self
131 }
132
133 pub fn identity_verified(mut self, verified: bool) -> Self {
135 self.identity_verified = verified;
136 self
137 }
138
139 pub fn security_audit_passed(mut self, passed: bool) -> Self {
141 self.security_audit_passed = passed;
142 self
143 }
144
145 pub fn open_source(mut self, is_open_source: bool) -> Self {
147 self.open_source = is_open_source;
148 self
149 }
150
151 pub fn total_interactions(mut self, count: u32) -> Self {
153 self.total_interactions = count;
154 self
155 }
156
157 pub fn total_reviews(mut self, count: u32) -> Self {
159 self.total_reviews = count;
160 self
161 }
162
163 pub fn average_rating(mut self, rating: f64) -> Self {
165 self.average_rating = Some(rating);
166 self
167 }
168
169 pub fn positive_reviews(mut self, count: u32) -> Self {
171 self.positive_reviews = count;
172 self
173 }
174
175 pub fn negative_reviews(mut self, count: u32) -> Self {
177 self.negative_reviews = count;
178 self
179 }
180
181 pub fn with_reviews(mut self, total: u32, average_rating: f64) -> Self {
199 self.total_reviews = total;
200
201 self.average_rating = Some(average_rating);
203
204 if total > 0 {
205 let positive_ratio = (average_rating.max(1.0).min(5.0) - 1.0) / 4.0;
208 self.positive_reviews = (total as f64 * positive_ratio).round() as u32;
209 self.negative_reviews = total.saturating_sub(self.positive_reviews);
210 } else {
211 self.positive_reviews = 0;
212 self.negative_reviews = 0;
213 }
214
215 self
216 }
217
218 pub fn build(self) -> Result<AgentData, BuilderError> {
228 if self.did.is_empty() {
230 return Err(BuilderError::InvalidField("DID cannot be empty".to_string()));
231 }
232 if !self.did.starts_with("did:") {
233 return Err(BuilderError::InvalidField("DID must start with 'did:' prefix".to_string()));
234 }
235 if self.did.contains("..") || self.did.contains("//") || self.did.contains('\'') ||
236 self.did.contains('"') || self.did.contains(';') || self.did.contains("--") ||
237 self.did.contains('\n') || self.did.contains('\t') || self.did.contains('<') ||
238 self.did.contains('>') || self.did.contains('\0') || self.did.contains('\r') ||
239 self.did.contains("javascript:") {
240 return Err(BuilderError::InvalidField("DID contains invalid format".to_string()));
241 }
242 if self.did.len() > 1000 {
243 return Err(BuilderError::InvalidField("DID exceeds maximum length".to_string()));
244 }
245 let parts: Vec<&str> = self.did.split(':').collect();
246 if parts.len() < 3 || parts[1].is_empty() || parts[2].is_empty() {
247 return Err(BuilderError::InvalidField("DID must have format 'did:method:id'".to_string()));
248 }
249
250 if self.created_at > Utc::now() {
252 return Err(BuilderError::InvalidField("Creation date cannot be in the future".to_string()));
253 }
254
255 let total_reviews = if self.total_reviews == 0 && (self.positive_reviews > 0 || self.negative_reviews > 0) {
257 self.positive_reviews + self.negative_reviews
258 } else {
259 self.total_reviews
260 };
261
262 if total_reviews > 0 && self.positive_reviews + self.negative_reviews > 0 {
264 if total_reviews != self.positive_reviews + self.negative_reviews {
265 return Err(BuilderError::InvalidField(
266 format!(
267 "Total reviews ({}) must equal positive ({}) + negative ({}) reviews",
268 total_reviews, self.positive_reviews, self.negative_reviews
269 )
270 ));
271 }
272 }
273
274 if total_reviews > self.total_interactions {
276 return Err(BuilderError::InvalidField(
277 format!(
278 "Total reviews ({}) cannot exceed total interactions ({})",
279 total_reviews, self.total_interactions
280 )
281 ));
282 }
283
284 let average_rating = match self.average_rating {
286 Some(rating) => {
287 if rating.is_nan() || rating.is_infinite() {
289 return Err(BuilderError::InvalidField(
290 format!("Average rating must be a valid number, got {}", rating)
291 ));
292 }
293 if rating < 1.0 || rating > 5.0 {
294 return Err(BuilderError::InvalidField(
295 format!("Average rating must be between 1.0 and 5.0, got {}", rating)
296 ));
297 }
298 Some(rating)
299 }
300 None => {
301 if total_reviews > 0 && (self.positive_reviews > 0 || self.negative_reviews > 0) {
302 let positive_ratio = self.positive_reviews as f64 / total_reviews as f64;
304 Some(1.0 + (positive_ratio * 4.0))
305 } else {
306 None
307 }
308 }
309 };
310
311 if let Some(level) = self.mcp_level {
313 if level > 3 {
314 return Err(BuilderError::InvalidField(
315 format!("MCP level must be between 0 and 3, got {}", level)
316 ));
317 }
318 }
319
320 Ok(AgentData {
321 did: self.did,
322 created_at: self.created_at,
323 mcp_level: self.mcp_level,
324 identity_verified: self.identity_verified,
325 security_audit_passed: self.security_audit_passed,
326 open_source: self.open_source,
327 total_interactions: self.total_interactions,
328 total_reviews,
329 average_rating,
330 positive_reviews: self.positive_reviews,
331 negative_reviews: self.negative_reviews,
332 })
333 }
334}
335
336#[derive(Debug, Clone, PartialEq)]
338pub enum BuilderError {
339 InvalidField(String),
341}
342
343impl std::fmt::Display for BuilderError {
344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345 match self {
346 BuilderError::InvalidField(msg) => write!(f, "Invalid field: {}", msg),
347 }
348 }
349}
350
351impl std::error::Error for BuilderError {}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use chrono::Duration;
357
358 #[test]
359 fn test_builder_minimal() {
360 let agent = AgentDataBuilder::new("did:example:123")
361 .build()
362 .unwrap();
363
364 assert_eq!(agent.did, "did:example:123");
365 assert_eq!(agent.mcp_level, None);
366 assert!(!agent.identity_verified);
367 assert!(!agent.security_audit_passed);
368 assert!(!agent.open_source);
369 assert_eq!(agent.total_interactions, 0);
370 assert_eq!(agent.total_reviews, 0);
371 assert_eq!(agent.average_rating, None);
372 assert_eq!(agent.positive_reviews, 0);
373 assert_eq!(agent.negative_reviews, 0);
374 }
375
376 #[test]
377 fn test_builder_with_all_fields() {
378 let created_at = Utc::now() - Duration::days(30);
379 let agent = AgentDataBuilder::new("did:example:456")
380 .created_at(created_at)
381 .mcp_level(3)
382 .identity_verified(true)
383 .security_audit_passed(true)
384 .open_source(true)
385 .total_interactions(1000)
386 .total_reviews(100)
387 .average_rating(4.2)
388 .positive_reviews(80)
389 .negative_reviews(20)
390 .build()
391 .unwrap();
392
393 assert_eq!(agent.did, "did:example:456");
394 assert_eq!(agent.created_at, created_at);
395 assert_eq!(agent.mcp_level, Some(3));
396 assert!(agent.identity_verified);
397 assert!(agent.security_audit_passed);
398 assert!(agent.open_source);
399 assert_eq!(agent.total_interactions, 1000);
400 assert_eq!(agent.total_reviews, 100);
401 assert_eq!(agent.average_rating, Some(4.2));
402 assert_eq!(agent.positive_reviews, 80);
403 assert_eq!(agent.negative_reviews, 20);
404 }
405
406 #[test]
407 fn test_with_reviews_helper() {
408 let agent = AgentDataBuilder::new("did:example:789")
409 .total_interactions(150)
410 .with_reviews(100, 4.5)
411 .build()
412 .unwrap();
413
414 assert_eq!(agent.total_reviews, 100);
415 assert_eq!(agent.average_rating, Some(4.5));
416 assert_eq!(agent.positive_reviews, 88); assert_eq!(agent.negative_reviews, 12);
418 }
419
420 #[test]
421 fn test_with_reviews_edge_cases() {
422 let agent = AgentDataBuilder::new("did:example:1")
424 .total_interactions(60)
425 .with_reviews(50, 5.0)
426 .build()
427 .unwrap();
428 assert_eq!(agent.positive_reviews, 50);
429 assert_eq!(agent.negative_reviews, 0);
430
431 let agent = AgentDataBuilder::new("did:example:2")
433 .total_interactions(60)
434 .with_reviews(50, 1.0)
435 .build()
436 .unwrap();
437 assert_eq!(agent.positive_reviews, 0);
438 assert_eq!(agent.negative_reviews, 50);
439
440 let result = AgentDataBuilder::new("did:example:3")
442 .with_reviews(100, 6.0)
443 .build();
444 assert!(result.is_err());
445
446 let result = AgentDataBuilder::new("did:example:4")
448 .with_reviews(100, 0.5)
449 .build();
450 assert!(result.is_err());
451 }
452
453 #[test]
454 fn test_validation_empty_did() {
455 let result = AgentDataBuilder::new("")
456 .build();
457
458 assert!(result.is_err());
459 match result.unwrap_err() {
460 BuilderError::InvalidField(msg) => assert!(msg.contains("DID cannot be empty")),
461 }
462 }
463
464 #[test]
465 fn test_validation_future_date() {
466 let result = AgentDataBuilder::new("did:example:123")
467 .created_at(Utc::now() + Duration::days(1))
468 .build();
469
470 assert!(result.is_err());
471 match result.unwrap_err() {
472 BuilderError::InvalidField(msg) => assert!(msg.contains("future")),
473 }
474 }
475
476 #[test]
477 fn test_validation_review_mismatch() {
478 let result = AgentDataBuilder::new("did:example:123")
479 .total_reviews(100)
480 .positive_reviews(60)
481 .negative_reviews(50) .build();
483
484 assert!(result.is_err());
485 match result.unwrap_err() {
486 BuilderError::InvalidField(msg) => assert!(msg.contains("must equal")),
487 }
488 }
489
490 #[test]
491 fn test_validation_invalid_rating() {
492 let result = AgentDataBuilder::new("did:example:123")
493 .average_rating(5.5)
494 .build();
495
496 assert!(result.is_err());
497 match result.unwrap_err() {
498 BuilderError::InvalidField(msg) => assert!(msg.contains("between 1.0 and 5.0")),
499 }
500 }
501
502 #[test]
503 fn test_validation_invalid_mcp_level() {
504 let result = AgentDataBuilder::new("did:example:123")
505 .mcp_level(10)
506 .build();
507
508 assert!(result.is_err());
509 match result.unwrap_err() {
510 BuilderError::InvalidField(msg) => assert!(msg.contains("MCP level")),
511 }
512 }
513
514 #[test]
515 fn test_auto_calculate_total_reviews() {
516 let agent = AgentDataBuilder::new("did:example:123")
517 .total_interactions(120)
518 .positive_reviews(75)
519 .negative_reviews(25)
520 .build()
521 .unwrap();
522
523 assert_eq!(agent.total_reviews, 100);
524 }
525
526 #[test]
527 fn test_auto_calculate_average_rating() {
528 let agent = AgentDataBuilder::new("did:example:123")
529 .total_interactions(120)
530 .positive_reviews(80)
531 .negative_reviews(20)
532 .build()
533 .unwrap();
534
535 assert_eq!(agent.average_rating, Some(4.2));
537 }
538
539 #[test]
540 fn test_builder_is_cloneable() {
541 let builder = AgentDataBuilder::new("did:example:123")
542 .mcp_level(2)
543 .identity_verified(true);
544
545 let builder2 = builder.clone();
546 let agent1 = builder.build().unwrap();
547 let agent2 = builder2.build().unwrap();
548
549 assert_eq!(agent1.did, agent2.did);
550 assert_eq!(agent1.mcp_level, agent2.mcp_level);
551 assert_eq!(agent1.identity_verified, agent2.identity_verified);
552 }
553
554 #[test]
555 fn test_method_chaining() {
556 let _agent = AgentDataBuilder::new("did:example:123")
558 .created_at(Utc::now())
559 .mcp_level(2)
560 .identity_verified(true)
561 .security_audit_passed(true)
562 .open_source(true)
563 .total_interactions(1000)
564 .with_reviews(100, 4.5)
565 .build()
566 .unwrap();
567 }
568
569 #[test]
570 fn test_builder_error_display() {
571 let error = BuilderError::InvalidField("test error".to_string());
572 assert_eq!(error.to_string(), "Invalid field: test error");
573
574 let _: &dyn std::error::Error = &error;
576 }
577}