use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScarSeverity {
Minor,
Moderate,
Severe,
Fatal,
}
#[derive(Debug)]
pub struct Scar {
timestamp: DateTime<Utc>,
severity: ScarSeverity,
description: String,
context: Option<String>,
}
impl Scar {
pub fn new(severity: ScarSeverity, description: String, context: Option<String>) -> Self {
Scar {
timestamp: Utc::now(),
severity,
description,
context,
}
}
pub fn timestamp(&self) -> DateTime<Utc> {
self.timestamp
}
pub fn severity(&self) -> ScarSeverity {
self.severity
}
pub fn description(&self) -> &str {
&self.description
}
pub fn context(&self) -> Option<&str> {
self.context.as_deref()
}
}
#[derive(Debug)]
pub struct ScarTissue {
scars: Vec<Scar>,
}
impl ScarTissue {
pub fn new() -> Self {
ScarTissue { scars: Vec::new() }
}
pub fn inflict(&mut self, severity: ScarSeverity, description: String, context: Option<String>) {
let scar = Scar::new(severity, description, context);
self.scars.push(scar);
}
pub fn all_scars(&self) -> &[Scar] {
&self.scars
}
pub fn scar_count(&self) -> usize {
self.scars.len()
}
pub fn scars_by_severity(&self, severity: ScarSeverity) -> Vec<&Scar> {
self.scars
.iter()
.filter(|scar| scar.severity() == severity)
.collect()
}
pub fn latest_scar(&self) -> Option<&Scar> {
self.scars.last()
}
pub fn has_fatal_scars(&self) -> bool {
self.scars
.iter()
.any(|scar| scar.severity() == ScarSeverity::Fatal)
}
pub fn damage_score(&self) -> u32 {
self.scars
.iter()
.map(|scar| match scar.severity() {
ScarSeverity::Minor => 1,
ScarSeverity::Moderate => 5,
ScarSeverity::Severe => 20,
ScarSeverity::Fatal => 100,
})
.sum()
}
}
impl Default for ScarTissue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scar_tissue_starts_clean() {
let tissue = ScarTissue::new();
assert_eq!(tissue.scar_count(), 0);
assert_eq!(tissue.damage_score(), 0);
assert!(!tissue.has_fatal_scars());
}
#[test]
fn inflicting_scars_increases_count() {
let mut tissue = ScarTissue::new();
tissue.inflict(ScarSeverity::Minor, "Minor error".to_string(), None);
assert_eq!(tissue.scar_count(), 1);
tissue.inflict(ScarSeverity::Moderate, "Moderate error".to_string(), None);
assert_eq!(tissue.scar_count(), 2);
tissue.inflict(ScarSeverity::Severe, "Severe error".to_string(), Some("Context".to_string()));
assert_eq!(tissue.scar_count(), 3);
}
#[test]
fn scars_are_permanent() {
let mut tissue = ScarTissue::new();
tissue.inflict(ScarSeverity::Minor, "Error 1".to_string(), None);
tissue.inflict(ScarSeverity::Minor, "Error 2".to_string(), None);
assert_eq!(tissue.scar_count(), 2);
assert_eq!(tissue.scar_count(), 2);
}
#[test]
fn scar_count_only_increases() {
let mut tissue = ScarTissue::new();
let count0 = tissue.scar_count();
tissue.inflict(ScarSeverity::Minor, "Scar 1".to_string(), None);
let count1 = tissue.scar_count();
tissue.inflict(ScarSeverity::Moderate, "Scar 2".to_string(), None);
let count2 = tissue.scar_count();
assert_eq!(count0, 0);
assert_eq!(count1, 1);
assert_eq!(count2, 2);
assert!(count1 > count0);
assert!(count2 > count1);
}
#[test]
fn damage_score_only_increases() {
let mut tissue = ScarTissue::new();
let score0 = tissue.damage_score();
tissue.inflict(ScarSeverity::Minor, "Damage 1".to_string(), None);
let score1 = tissue.damage_score();
tissue.inflict(ScarSeverity::Moderate, "Damage 2".to_string(), None);
let score2 = tissue.damage_score();
tissue.inflict(ScarSeverity::Severe, "Damage 3".to_string(), None);
let score3 = tissue.damage_score();
assert_eq!(score0, 0);
assert!(score1 > score0); assert!(score2 > score1); assert!(score3 > score2);
}
#[test]
fn scar_descriptions_are_immutable() {
let mut tissue = ScarTissue::new();
tissue.inflict(ScarSeverity::Minor, "Original error".to_string(), None);
let scars = tissue.all_scars();
let desc1 = scars[0].description();
let desc2 = scars[0].description();
assert_eq!(desc1, desc2);
assert_eq!(desc1, "Original error");
}
#[test]
fn fatal_scar_status_is_permanent() {
let mut tissue = ScarTissue::new();
assert!(!tissue.has_fatal_scars());
tissue.inflict(ScarSeverity::Fatal, "Fatal error".to_string(), None);
assert!(tissue.has_fatal_scars());
assert!(tissue.has_fatal_scars());
assert!(tissue.has_fatal_scars());
}
#[test]
fn scars_maintain_chronological_order() {
let mut tissue = ScarTissue::new();
tissue.inflict(ScarSeverity::Minor, "First".to_string(), None);
std::thread::sleep(std::time::Duration::from_millis(10));
tissue.inflict(ScarSeverity::Moderate, "Second".to_string(), None);
std::thread::sleep(std::time::Duration::from_millis(10));
tissue.inflict(ScarSeverity::Severe, "Third".to_string(), None);
let scars = tissue.all_scars();
assert_eq!(scars[0].description(), "First");
assert_eq!(scars[1].description(), "Second");
assert_eq!(scars[2].description(), "Third");
assert!(scars[0].timestamp() < scars[1].timestamp());
assert!(scars[1].timestamp() < scars[2].timestamp());
}
#[test]
fn fatal_scars_are_detected() {
let mut tissue = ScarTissue::new();
assert!(!tissue.has_fatal_scars());
tissue.inflict(ScarSeverity::Minor, "Minor".to_string(), None);
assert!(!tissue.has_fatal_scars());
tissue.inflict(ScarSeverity::Fatal, "Fatal error".to_string(), None);
assert!(tissue.has_fatal_scars());
}
#[test]
fn damage_score_accumulates() {
let mut tissue = ScarTissue::new();
assert_eq!(tissue.damage_score(), 0);
tissue.inflict(ScarSeverity::Minor, "".to_string(), None); assert_eq!(tissue.damage_score(), 1);
tissue.inflict(ScarSeverity::Moderate, "".to_string(), None); assert_eq!(tissue.damage_score(), 6);
tissue.inflict(ScarSeverity::Severe, "".to_string(), None); assert_eq!(tissue.damage_score(), 26);
tissue.inflict(ScarSeverity::Fatal, "".to_string(), None); assert_eq!(tissue.damage_score(), 126);
}
#[test]
fn scars_by_severity_filters_correctly() {
let mut tissue = ScarTissue::new();
tissue.inflict(ScarSeverity::Minor, "Minor 1".to_string(), None);
tissue.inflict(ScarSeverity::Severe, "Severe 1".to_string(), None);
tissue.inflict(ScarSeverity::Minor, "Minor 2".to_string(), None);
tissue.inflict(ScarSeverity::Moderate, "Moderate 1".to_string(), None);
let minor_scars = tissue.scars_by_severity(ScarSeverity::Minor);
assert_eq!(minor_scars.len(), 2);
let severe_scars = tissue.scars_by_severity(ScarSeverity::Severe);
assert_eq!(severe_scars.len(), 1);
let fatal_scars = tissue.scars_by_severity(ScarSeverity::Fatal);
assert_eq!(fatal_scars.len(), 0);
}
}