use super::BasePlatformValidator;
use super::*;
pub struct WebValidator {
base: BasePlatformValidator,
#[allow(dead_code)]
browser_automation: BrowserAutomation,
#[allow(dead_code)]
ui_analyzer: UIAnalyzer,
#[allow(dead_code)]
accessibility_checker: AccessibilityChecker,
#[allow(dead_code)]
performance_profiler: WebPerformanceProfiler,
}
impl Default for WebValidator {
fn default() -> Self {
Self::new()
}
}
impl WebValidator {
pub fn new() -> Self {
Self {
base: BasePlatformValidator::new(Platform::Web),
browser_automation: BrowserAutomation::new(),
ui_analyzer: UIAnalyzer::new(),
accessibility_checker: AccessibilityChecker::new(),
performance_profiler: WebPerformanceProfiler::new(),
}
}
async fn validate_web_protocol(
&self,
protocol_content: &str,
config: &ValidationConfig,
) -> Result<WebValidationResult, VIBEError> {
let start_time = std::time::Instant::now();
let web_elements = self.extract_web_elements(protocol_content)?;
let ui_validation = self.validate_ui_components(&web_elements, config).await?;
let responsive_validation = self
.validate_responsive_design(&web_elements, config)
.await?;
let accessibility_validation = self.validate_accessibility(&web_elements, config).await?;
let performance_validation = self.validate_performance(&web_elements, config).await?;
let browser_validation = self
.validate_cross_browser_compatibility(&web_elements, config)
.await?;
let validation_time = start_time.elapsed().as_millis() as u64;
let overall_score = self.calculate_web_score(&[
&ui_validation,
&responsive_validation,
&accessibility_validation,
&performance_validation,
&browser_validation,
])?;
let mut all_issues = Vec::new();
all_issues.extend(ui_validation.issues);
all_issues.extend(responsive_validation.issues);
all_issues.extend(accessibility_validation.issues);
all_issues.extend(performance_validation.issues);
all_issues.extend(browser_validation.issues);
let recommendations = self.generate_web_recommendations(&all_issues, overall_score)?;
Ok(WebValidationResult {
overall_score,
ui_score: ui_validation.score,
responsive_score: responsive_validation.score,
accessibility_score: accessibility_validation.score,
performance_score: performance_validation.score,
browser_score: browser_validation.score,
validation_time_ms: validation_time,
issues: all_issues,
recommendations,
web_specific_metrics: WebSpecificMetrics {
load_time_ms: performance_validation.load_time_ms,
accessibility_compliance: accessibility_validation.compliance_percentage,
responsive_breakpoints: responsive_validation.breakpoints_tested,
browser_compatibility: browser_validation.compatible_browsers,
seo_score: self.calculate_seo_score(&web_elements),
},
})
}
fn extract_web_elements(&self, content: &str) -> Result<WebElements, VIBEError> {
let mut elements = WebElements::default();
let html_pattern = regex::Regex::new(r"<(\w+)[^>]*>").unwrap();
for cap in html_pattern.captures_iter(content) {
elements.html_tags.insert(cap[1].to_string());
}
let css_pattern = regex::Regex::new(r"(\w+)\s*:\s*([^;]+)").unwrap();
for cap in css_pattern.captures_iter(content) {
elements
.css_properties
.insert(cap[1].to_string(), cap[2].to_string());
}
let js_pattern = regex::Regex::new(r"function\s+(\w+)\s*\(").unwrap();
for cap in js_pattern.captures_iter(content) {
elements.javascript_functions.insert(cap[1].to_string());
}
let ui_pattern =
regex::Regex::new(r"(button|input|form|nav|header|footer|menu|modal|dialog)").unwrap();
let content_lower = content.to_lowercase();
for cap in ui_pattern.captures_iter(&content_lower) {
elements.ui_components.insert(cap[1].to_string());
}
if content.contains("@media")
|| content.contains("responsive")
|| content.contains("mobile")
{
elements.responsive_design = true;
}
if content.contains("alt") || content.contains("aria") || content.contains("accessibility")
{
elements.accessibility_features = true;
}
if content.contains("lazy") || content.contains("cache") || content.contains("optimize") {
elements.performance_optimization = true;
}
Ok(elements)
}
async fn validate_ui_components(
&self,
elements: &WebElements,
_config: &ValidationConfig,
) -> Result<UIValidationResult, VIBEError> {
let mut score: f32 = 100.0;
let mut issues = Vec::new();
let required_components = vec!["button", "input", "form"];
for component in &required_components {
if !elements.ui_components.contains(*component) {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Medium,
category: IssueCategory::UIUXIssue,
description: format!("Missing essential UI component: {}", component),
location: None,
suggestion: Some(format!("Add {} component to the interface", component)),
});
score -= 10.0;
}
}
if self.has_contrast_issues(elements) {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Medium,
category: IssueCategory::UIUXIssue,
description: "Potential color contrast issues detected".to_string(),
location: None,
suggestion: Some("Ensure WCAG AA contrast ratio compliance".to_string()),
});
score -= 8.0;
}
if !self.has_proper_forms(elements) {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::High,
category: IssueCategory::UIUXIssue,
description: "Form validation or labeling issues detected".to_string(),
location: None,
suggestion: Some("Add proper form validation and labels".to_string()),
});
score -= 15.0;
}
Ok(UIValidationResult {
score: score.clamp(0.0, 100.0),
issues,
})
}
async fn validate_responsive_design(
&self,
elements: &WebElements,
_config: &ValidationConfig,
) -> Result<ResponsiveValidationResult, VIBEError> {
let mut score: f32 = 100.0;
let mut issues = Vec::new();
let mut breakpoints_tested = Vec::new();
if !elements.responsive_design {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::High,
category: IssueCategory::UIUXIssue,
description: "No responsive design indicators found".to_string(),
location: None,
suggestion: Some("Implement responsive design with @media queries".to_string()),
});
score -= 25.0;
} else {
let common_breakpoints = vec![320, 768, 1024, 1200];
for bp in &common_breakpoints {
breakpoints_tested.push(*bp);
if self.simulate_breakpoint_test(*bp) {
} else {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Medium,
category: IssueCategory::UIUXIssue,
description: format!("Layout issues at {}px breakpoint", bp),
location: None,
suggestion: Some("Fix layout for this screen size".to_string()),
});
score -= 5.0;
}
}
}
if !self.has_mobile_first_approach(elements) {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Low,
category: IssueCategory::UIUXIssue,
description: "Mobile-first approach not evident".to_string(),
location: None,
suggestion: Some("Consider implementing mobile-first CSS".to_string()),
});
score -= 5.0;
}
Ok(ResponsiveValidationResult {
score: score.clamp(0.0, 100.0),
issues,
breakpoints_tested,
})
}
async fn validate_accessibility(
&self,
elements: &WebElements,
_config: &ValidationConfig,
) -> Result<AccessibilityValidationResult, VIBEError> {
let mut score: f32 = 100.0;
let mut issues = Vec::new();
if !elements.accessibility_features {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::High,
category: IssueCategory::UIUXIssue,
description: "No accessibility features detected".to_string(),
location: None,
suggestion: Some(
"Add alt attributes, ARIA labels, and keyboard navigation".to_string(),
),
});
score -= 30.0;
}
let compliance_percentage = self.simulate_wcag_compliance(elements);
if compliance_percentage < 80.0 {
score = compliance_percentage;
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::High,
category: IssueCategory::UIUXIssue,
description: format!(
"WCAG compliance at {}% (below 80% threshold)",
compliance_percentage
),
location: None,
suggestion: Some("Improve accessibility to meet WCAG AA standards".to_string()),
});
}
Ok(AccessibilityValidationResult {
score: score.clamp(0.0, 100.0),
issues,
compliance_percentage,
})
}
async fn validate_performance(
&self,
elements: &WebElements,
_config: &ValidationConfig,
) -> Result<PerformanceValidationResult, VIBEError> {
let mut score: f32 = 100.0;
let mut issues = Vec::new();
let load_time_ms = self.simulate_load_time(elements);
if load_time_ms > 3000 {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::High,
category: IssueCategory::PerformanceIssue,
description: format!(
"Page load time {}ms exceeds 3 second threshold",
load_time_ms
),
location: None,
suggestion: Some("Optimize images, minify CSS/JS, enable compression".to_string()),
});
score -= 20.0;
} else if load_time_ms > 1500 {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Medium,
category: IssueCategory::PerformanceIssue,
description: format!("Page load time {}ms could be improved", load_time_ms),
location: None,
suggestion: Some("Consider performance optimizations".to_string()),
});
score -= 10.0;
}
if !elements.performance_optimization {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Low,
category: IssueCategory::PerformanceIssue,
description: "No performance optimization indicators found".to_string(),
location: None,
suggestion: Some(
"Consider implementing lazy loading, caching, and optimization".to_string(),
),
});
score -= 8.0;
}
Ok(PerformanceValidationResult {
score: score.clamp(0.0, 100.0),
issues,
load_time_ms,
})
}
async fn validate_cross_browser_compatibility(
&self,
elements: &WebElements,
_config: &ValidationConfig,
) -> Result<BrowserValidationResult, VIBEError> {
let mut score: f32 = 100.0;
let mut issues = Vec::new();
let compatible_browsers = vec!["Chrome", "Firefox", "Safari", "Edge"];
let mut tested_browsers = Vec::new();
for browser in &compatible_browsers {
tested_browsers.push(browser.to_string());
if !self.simulate_browser_compatibility(browser, elements) {
issues.push(ValidationIssue {
platform: Platform::Web,
severity: Severity::Medium,
category: IssueCategory::CompatibilityProblem,
description: format!("Compatibility issues detected in {}", browser),
location: None,
suggestion: Some(format!("Fix {} compatibility issues", browser)),
});
score -= 10.0;
}
}
Ok(BrowserValidationResult {
score: score.clamp(0.0, 100.0),
issues,
compatible_browsers: tested_browsers,
})
}
fn calculate_web_score(&self, scores: &[&dyn WebScoreComponent]) -> Result<f32, VIBEError> {
if scores.is_empty() {
return Err(VIBEError::ValidationError(
"No validation components provided".to_string(),
));
}
let weights = [0.25, 0.20, 0.20, 0.20, 0.15];
let mut weighted_sum = 0.0;
let mut total_weight = 0.0;
for (i, component) in scores.iter().enumerate() {
if i < weights.len() {
let score = component.get_score();
let weight = weights[i];
weighted_sum += score * weight;
total_weight += weight;
}
}
Ok(weighted_sum / total_weight)
}
fn generate_web_recommendations(
&self,
issues: &[ValidationIssue],
overall_score: f32,
) -> Result<Vec<String>, VIBEError> {
let mut recommendations = Vec::new();
if overall_score < 70.0 {
recommendations
.push("Implement comprehensive web performance optimizations".to_string());
}
let accessibility_issues = issues
.iter()
.filter(|i| {
i.category == IssueCategory::UIUXIssue
&& (i.description.contains("accessibility") || i.description.contains("WCAG"))
})
.count();
if accessibility_issues > 0 {
recommendations.push(
"Prioritize accessibility improvements for better user experience".to_string(),
);
}
let responsive_issues = issues
.iter()
.filter(|i| {
i.category == IssueCategory::UIUXIssue && i.description.contains("responsive")
})
.count();
if responsive_issues > 0 {
recommendations
.push("Enhance responsive design for better mobile experience".to_string());
}
if overall_score < 60.0 {
recommendations
.push("Focus on core web vitals: LCP, FID, and CLS optimization".to_string());
recommendations
.push("Implement progressive web app features for better performance".to_string());
}
Ok(recommendations)
}
fn has_contrast_issues(&self, elements: &WebElements) -> bool {
elements.css_properties.contains_key("color")
&& !elements.css_properties.contains_key("background-color")
}
fn has_proper_forms(&self, elements: &WebElements) -> bool {
elements.ui_components.contains("form") && elements.html_tags.contains("input")
}
fn has_mobile_first_approach(&self, elements: &WebElements) -> bool {
elements.css_properties.contains_key("max-width")
|| elements.css_properties.contains_key("min-width")
}
fn simulate_breakpoint_test(&self, _breakpoint: u32) -> bool {
true }
fn simulate_wcag_compliance(&self, elements: &WebElements) -> f32 {
if elements.accessibility_features {
85.0 } else {
45.0 }
}
fn simulate_load_time(&self, elements: &WebElements) -> u64 {
let base_time = 1000;
let complexity_factor =
elements.html_tags.len() as u64 + elements.javascript_functions.len() as u64;
base_time + complexity_factor * 50
}
fn simulate_browser_compatibility(&self, browser: &str, elements: &WebElements) -> bool {
match browser {
"Chrome" => true, "Firefox" => true, "Safari" => elements.javascript_functions.len() < 10, "Edge" => true, _ => true,
}
}
fn calculate_seo_score(&self, elements: &WebElements) -> f32 {
let mut score: f32 = 50.0;
let semantic_tags = vec![
"header", "nav", "main", "article", "section", "aside", "footer",
];
for tag in &semantic_tags {
if elements.html_tags.contains(*tag) {
score += 5.0;
}
}
if elements.html_tags.contains("meta") {
score += 10.0;
}
score.clamp(0.0, 100.0)
}
}
#[async_trait::async_trait]
impl PlatformValidator for WebValidator {
async fn validate_protocol(
&self,
protocol_content: &str,
config: &ValidationConfig,
platform: Platform,
) -> Result<PlatformValidationResult, VIBEError> {
if platform != Platform::Web {
return Err(VIBEError::PlatformError(
"WebValidator can only validate Web platform protocols".to_string(),
));
}
let common_result = self
.base
.perform_common_validation(protocol_content, config)
.await?;
let web_result = self.validate_web_protocol(protocol_content, config).await?;
let final_score = (common_result.score + web_result.overall_score) / 2.0;
let mut all_issues = common_result.issues;
all_issues.extend(web_result.issues);
let recommendations = self.generate_web_recommendations(&all_issues, final_score)?;
Ok(PlatformValidationResult {
platform: Platform::Web,
score: final_score,
status: if final_score >= config.minimum_score {
ValidationStatus::Passed
} else {
ValidationStatus::Failed
},
issues: all_issues,
performance_metrics: PlatformPerformanceMetrics {
average_response_time_ms: web_result.validation_time_ms,
memory_usage_mb: 150,
cpu_usage_percent: 25.0,
error_rate_percent: 2.0,
throughput_requests_per_second: 10.0,
},
recommendations,
})
}
fn get_capabilities(&self) -> PlatformCapabilities {
self.base.capabilities.clone()
}
fn get_requirements(&self) -> PlatformRequirements {
self.base.requirements.clone()
}
fn estimate_complexity(&self, protocol_content: &str) -> ValidationComplexity {
self.base
.complexity_estimator
.estimate_complexity(protocol_content)
}
fn get_scoring_criteria(&self) -> PlatformScoringCriteria {
PlatformScoringCriteria {
primary_criteria: vec![
"UI/UX Quality".to_string(),
"Responsive Design".to_string(),
"Accessibility Compliance".to_string(),
],
secondary_criteria: vec![
"Performance".to_string(),
"Cross-browser Compatibility".to_string(),
"SEO Optimization".to_string(),
],
penalty_factors: HashMap::from([
("missing_accessibility".to_string(), 0.2),
("poor_performance".to_string(), 0.15),
("no_responsive_design".to_string(), 0.25),
]),
bonus_factors: HashMap::from([
("wcag_compliant".to_string(), 0.1),
("fast_load_time".to_string(), 0.05),
("good_seo".to_string(), 0.05),
]),
}
}
}
#[derive(Debug, Default)]
struct WebElements {
html_tags: HashSet<String>,
css_properties: HashMap<String, String>,
javascript_functions: HashSet<String>,
ui_components: HashSet<String>,
responsive_design: bool,
accessibility_features: bool,
performance_optimization: bool,
}
#[derive(Debug)]
struct UIValidationResult {
score: f32,
issues: Vec<ValidationIssue>,
}
#[derive(Debug)]
struct ResponsiveValidationResult {
score: f32,
issues: Vec<ValidationIssue>,
breakpoints_tested: Vec<u32>,
}
#[derive(Debug)]
struct AccessibilityValidationResult {
score: f32,
issues: Vec<ValidationIssue>,
compliance_percentage: f32,
}
#[derive(Debug)]
struct PerformanceValidationResult {
score: f32,
issues: Vec<ValidationIssue>,
load_time_ms: u64,
}
#[derive(Debug)]
struct BrowserValidationResult {
score: f32,
issues: Vec<ValidationIssue>,
compatible_browsers: Vec<String>,
}
#[derive(Debug)]
struct WebValidationResult {
overall_score: f32,
#[allow(dead_code)]
ui_score: f32,
#[allow(dead_code)]
responsive_score: f32,
#[allow(dead_code)]
accessibility_score: f32,
#[allow(dead_code)]
performance_score: f32,
#[allow(dead_code)]
browser_score: f32,
validation_time_ms: u64,
issues: Vec<ValidationIssue>,
#[allow(dead_code)]
recommendations: Vec<String>,
#[allow(dead_code)]
web_specific_metrics: WebSpecificMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct WebSpecificMetrics {
load_time_ms: u64,
accessibility_compliance: f32,
responsive_breakpoints: Vec<u32>,
browser_compatibility: Vec<String>,
seo_score: f32,
}
trait WebScoreComponent {
fn get_score(&self) -> f32;
}
impl WebScoreComponent for UIValidationResult {
fn get_score(&self) -> f32 {
self.score
}
}
impl WebScoreComponent for ResponsiveValidationResult {
fn get_score(&self) -> f32 {
self.score
}
}
impl WebScoreComponent for AccessibilityValidationResult {
fn get_score(&self) -> f32 {
self.score
}
}
impl WebScoreComponent for PerformanceValidationResult {
fn get_score(&self) -> f32 {
self.score
}
}
impl WebScoreComponent for BrowserValidationResult {
fn get_score(&self) -> f32 {
self.score
}
}
struct BrowserAutomation;
struct UIAnalyzer;
struct AccessibilityChecker;
struct WebPerformanceProfiler;
impl BrowserAutomation {
fn new() -> Self {
Self
}
}
impl UIAnalyzer {
fn new() -> Self {
Self
}
}
impl AccessibilityChecker {
fn new() -> Self {
Self
}
}
impl WebPerformanceProfiler {
fn new() -> Self {
Self
}
}
use std::collections::HashSet;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_web_validator_creation() {
let validator = WebValidator::new();
assert_eq!(validator.base.platform, Platform::Web);
}
#[test]
fn test_web_elements_extraction() {
let validator = WebValidator::new();
let content =
"<html><head><title>Test</title></head><body><button>Click</button></body></html>";
let elements = validator.extract_web_elements(content).unwrap();
assert!(elements.html_tags.contains("html"));
assert!(elements.html_tags.contains("button"));
assert!(elements.ui_components.contains("button"));
}
#[test]
fn test_seo_score_calculation() {
let validator = WebValidator::new();
let elements = WebElements {
html_tags: HashSet::from(["header".to_string(), "main".to_string()]),
css_properties: HashMap::new(),
javascript_functions: HashSet::new(),
ui_components: HashSet::new(),
responsive_design: false,
accessibility_features: false,
performance_optimization: false,
};
let seo_score = validator.calculate_seo_score(&elements);
assert!(seo_score > 50.0); }
}