use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Severity {
Error,
Warning,
Info,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Category {
Accessibility,
SEO,
Performance,
BestPractices,
Environment,
Safety,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
pub issue_type: String,
pub element_snippet: Option<String>,
pub suggested_fix: Option<String>,
pub severity: Severity,
pub fix_example: Option<String>,
pub category: Category,
}
impl Issue {
pub fn new(
issue_type: &str,
element_snippet: Option<&str>,
suggested_fix: Option<&str>,
severity: Severity,
fix_example: Option<&str>,
category: Category,
) -> Self {
Self {
issue_type: issue_type.to_string(),
element_snippet: element_snippet.map(|s| s.to_string()),
suggested_fix: suggested_fix.map(|s| s.to_string()),
severity,
fix_example: fix_example.map(|s| s.to_string()),
category,
}
}
}
#[derive(Debug, Clone)]
pub struct PageLoadResult {
pub document: scraper::Html,
pub load_time: f64,
pub request_count: u32,
pub page_size: u64,
pub is_compressed: bool,
pub has_caching_headers: bool,
pub website_url: Option<String>,
}
impl PageLoadResult {
pub fn new(
document: scraper::Html,
load_time: f64,
request_count: u32,
page_size: u64,
is_compressed: bool,
has_caching_headers: bool,
website_url: Option<&str>,
) -> Self {
Self {
document,
load_time,
request_count,
page_size,
is_compressed,
has_caching_headers,
website_url: website_url.map(|s| s.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Report {
pub issues: Vec<Issue>,
pub total_issues: usize,
pub error_count: usize,
pub warning_count: usize,
pub info_count: usize,
pub accessibility_score: u32,
pub seo_score: u32,
pub performance_score: u32,
pub best_practices_score: u32,
pub environment_score: u32,
pub safety_score: u32,
pub compliance_status: String,
pub website_url: Option<String>,
pub page_load_time: f64,
pub request_count: u32,
pub page_size: u64,
pub energy_consumption_kwh: f64,
pub co2_emissions_grams: f64,
pub environmental_rating: String,
pub uses_cdn: bool,
}
impl Report {
pub fn new(website_url: Option<&str>) -> Self {
Self {
issues: Vec::new(),
total_issues: 0,
error_count: 0,
warning_count: 0,
info_count: 0,
accessibility_score: 0,
seo_score: 0,
performance_score: 0,
best_practices_score: 0,
environment_score: 0,
safety_score: 0,
compliance_status: "Unknown".to_string(),
website_url: website_url.map(|s| s.to_string()),
page_load_time: 0.0,
request_count: 0,
page_size: 0,
energy_consumption_kwh: 0.0,
co2_emissions_grams: 0.0,
environmental_rating: "Unknown".to_string(),
uses_cdn: false,
}
}
pub fn recalculate(&mut self) {
self.total_issues = self.issues.len();
self.error_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Error)).count();
self.warning_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Warning)).count();
self.info_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Info)).count();
let accessibility_issues: Vec<_> = self.issues.iter()
.filter(|i| matches!(i.category, Category::Accessibility))
.collect();
let total_acc_issues = accessibility_issues.len() as f64;
let critical_issues = accessibility_issues.iter()
.filter(|i| matches!(i.severity, Severity::Error))
.count() as f64;
let warning_issues = accessibility_issues.iter()
.filter(|i| matches!(i.severity, Severity::Warning))
.count() as f64;
let base_score = 100.0;
let critical_penalty = critical_issues * 3.0;
let warning_penalty = warning_issues * 1.0;
let volume_penalty = if total_acc_issues > 10.0 { (total_acc_issues - 10.0) * 0.5 } else { 0.0 };
self.accessibility_score = ((base_score - critical_penalty - warning_penalty - volume_penalty)
.max(0.0).min(100.0)) as u32;
self.compliance_status = if self.accessibility_score >= 95 {
"Fully Compliant"
} else if self.accessibility_score >= 80 {
"Mostly Compliant"
} else if self.accessibility_score >= 60 {
"Partially Compliant"
} else {
"Not Compliant"
}.to_string();
}
}