Skip to main content

axis_core/
models.rs

1use serde::{Deserialize, Serialize};
2
3/// Severity levels for accessibility issues
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5pub enum Severity {
6    Error,
7    Warning,
8    Info,
9}
10
11/// Categories of accessibility issues
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub enum Category {
14    Accessibility,
15    SEO,
16    Performance,
17    BestPractices,
18    Environment,
19    Safety,
20}
21
22/// Represents an accessibility issue found during analysis
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Issue {
25    /// Type of issue (e.g., "Missing Alt Text")
26    pub issue_type: String,
27    /// HTML snippet where the issue was found
28    pub element_snippet: Option<String>,
29    /// Suggested fix for the issue
30    pub suggested_fix: Option<String>,
31    /// Severity level of the issue
32    pub severity: Severity,
33    /// Example of how to fix the issue
34    pub fix_example: Option<String>,
35    /// Category this issue belongs to
36    pub category: Category,
37}
38
39impl Issue {
40    pub fn new(
41        issue_type: &str,
42        element_snippet: Option<&str>,
43        suggested_fix: Option<&str>,
44        severity: Severity,
45        fix_example: Option<&str>,
46        category: Category,
47    ) -> Self {
48        Self {
49            issue_type: issue_type.to_string(),
50            element_snippet: element_snippet.map(|s| s.to_string()),
51            suggested_fix: suggested_fix.map(|s| s.to_string()),
52            severity,
53            fix_example: fix_example.map(|s| s.to_string()),
54            category,
55        }
56    }
57}
58
59/// Result of loading a web page
60#[derive(Debug, Clone)]
61pub struct PageLoadResult {
62    /// Parsed HTML document
63    pub document: scraper::Html,
64    /// Time taken to load the page (in seconds)
65    pub load_time: f64,
66    /// Number of HTTP requests made
67    pub request_count: u32,
68    /// Size of the page in bytes
69    pub page_size: u64,
70    /// Whether the page uses compression
71    pub is_compressed: bool,
72    /// Whether the page has caching headers
73    pub has_caching_headers: bool,
74    /// Optional website URL
75    pub website_url: Option<String>,
76}
77
78impl PageLoadResult {
79    pub fn new(
80        document: scraper::Html,
81        load_time: f64,
82        request_count: u32,
83        page_size: u64,
84        is_compressed: bool,
85        has_caching_headers: bool,
86        website_url: Option<&str>,
87    ) -> Self {
88        Self {
89            document,
90            load_time,
91            request_count,
92            page_size,
93            is_compressed,
94            has_caching_headers,
95            website_url: website_url.map(|s| s.to_string()),
96        }
97    }
98}
99
100/// Comprehensive accessibility report
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct Report {
103    /// List of issues found
104    pub issues: Vec<Issue>,
105    /// Total number of issues
106    pub total_issues: usize,
107    /// Number of error-level issues
108    pub error_count: usize,
109    /// Number of warning-level issues
110    pub warning_count: usize,
111    /// Number of info-level issues
112    pub info_count: usize,
113    /// Accessibility score (0-100)
114    pub accessibility_score: u32,
115    /// SEO score (0-100)
116    pub seo_score: u32,
117    /// Performance score (0-100)
118    pub performance_score: u32,
119    /// Best practices score (0-100)
120    pub best_practices_score: u32,
121    /// Environment score (0-100)
122    pub environment_score: u32,
123    /// Safety score (0-100)
124    pub safety_score: u32,
125    /// Overall compliance status
126    pub compliance_status: String,
127    /// Website URL that was analyzed
128    pub website_url: Option<String>,
129    /// Page load time in seconds
130    pub page_load_time: f64,
131    /// Number of HTTP requests
132    pub request_count: u32,
133    /// Page size in bytes
134    pub page_size: u64,
135    /// Estimated energy consumption in kWh
136    pub energy_consumption_kwh: f64,
137    /// Estimated CO₂ emissions in grams
138    pub co2_emissions_grams: f64,
139    /// Environmental rating
140    pub environmental_rating: String,
141    /// Whether the site uses CDN
142    pub uses_cdn: bool,
143}
144
145impl Report {
146    pub fn new(website_url: Option<&str>) -> Self {
147        Self {
148            issues: Vec::new(),
149            total_issues: 0,
150            error_count: 0,
151            warning_count: 0,
152            info_count: 0,
153            accessibility_score: 0,
154            seo_score: 0,
155            performance_score: 0,
156            best_practices_score: 0,
157            environment_score: 0,
158            safety_score: 0,
159            compliance_status: "Unknown".to_string(),
160            website_url: website_url.map(|s| s.to_string()),
161            page_load_time: 0.0,
162            request_count: 0,
163            page_size: 0,
164            energy_consumption_kwh: 0.0,
165            co2_emissions_grams: 0.0,
166            environmental_rating: "Unknown".to_string(),
167            uses_cdn: false,
168        }
169    }
170
171    /// Recalculate counts and scores based on current issues
172    pub fn recalculate(&mut self) {
173        self.total_issues = self.issues.len();
174        self.error_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Error)).count();
175        self.warning_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Warning)).count();
176        self.info_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Info)).count();
177
178        // Calculate accessibility score
179        let accessibility_issues: Vec<_> = self.issues.iter()
180            .filter(|i| matches!(i.category, Category::Accessibility))
181            .collect();
182
183        let total_acc_issues = accessibility_issues.len() as f64;
184        let critical_issues = accessibility_issues.iter()
185            .filter(|i| matches!(i.severity, Severity::Error))
186            .count() as f64;
187        let warning_issues = accessibility_issues.iter()
188            .filter(|i| matches!(i.severity, Severity::Warning))
189            .count() as f64;
190
191        let base_score = 100.0;
192        let critical_penalty = critical_issues * 3.0;
193        let warning_penalty = warning_issues * 1.0;
194        let volume_penalty = if total_acc_issues > 10.0 { (total_acc_issues - 10.0) * 0.5 } else { 0.0 };
195
196        self.accessibility_score = ((base_score - critical_penalty - warning_penalty - volume_penalty)
197            .max(0.0).min(100.0)) as u32;
198
199        // Set compliance status based on accessibility score
200        self.compliance_status = if self.accessibility_score >= 95 {
201            "Fully Compliant"
202        } else if self.accessibility_score >= 80 {
203            "Mostly Compliant"
204        } else if self.accessibility_score >= 60 {
205            "Partially Compliant"
206        } else {
207            "Not Compliant"
208        }.to_string();
209    }
210}