Skip to main content

ppt_rs/core/package_validation/
report.rs

1//! Structured validation report types.
2
3/// Severity of a package validation finding.
4#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
5pub enum ValidationSeverity {
6    Warning,
7    Error,
8}
9
10/// Category grouping for validation findings (used in tests and filtering).
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ValidationCategory {
13    MissingPart,
14    Relationship,
15    ContentType,
16    Presentation,
17    SlideMaster,
18    Slide,
19    Chart,
20    Xml,
21    Theme,
22}
23
24/// A single structural issue found in a PPTX package.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct PackageValidationIssue {
27    pub category: ValidationCategory,
28    pub severity: ValidationSeverity,
29    pub message: String,
30    /// Part path most relevant to the issue, when applicable.
31    pub path: Option<String>,
32}
33
34impl PackageValidationIssue {
35    pub fn error(
36        category: ValidationCategory,
37        message: impl Into<String>,
38        path: Option<&str>,
39    ) -> Self {
40        Self {
41            category,
42            severity: ValidationSeverity::Error,
43            message: message.into(),
44            path: path.map(str::to_string),
45        }
46    }
47
48    pub fn warning(
49        category: ValidationCategory,
50        message: impl Into<String>,
51        path: Option<&str>,
52    ) -> Self {
53        Self {
54            category,
55            severity: ValidationSeverity::Warning,
56            message: message.into(),
57            path: path.map(str::to_string),
58        }
59    }
60}
61
62/// Aggregated result of running all package validation rules.
63#[derive(Clone, Debug, Default, PartialEq, Eq)]
64pub struct PackageValidationReport {
65    pub issues: Vec<PackageValidationIssue>,
66}
67
68impl PackageValidationReport {
69    pub fn is_valid(&self) -> bool {
70        self.issues
71            .iter()
72            .all(|i| i.severity != ValidationSeverity::Error)
73    }
74
75    pub fn is_ok(&self) -> bool {
76        self.is_valid()
77    }
78
79    pub fn error_count(&self) -> usize {
80        self.issues
81            .iter()
82            .filter(|i| i.severity == ValidationSeverity::Error)
83            .count()
84    }
85
86    pub fn warning_count(&self) -> usize {
87        self.issues
88            .iter()
89            .filter(|i| i.severity == ValidationSeverity::Warning)
90            .count()
91    }
92
93    pub fn push(&mut self, issue: PackageValidationIssue) {
94        self.issues.push(issue);
95    }
96
97    pub fn extend(&mut self, issues: impl IntoIterator<Item = PackageValidationIssue>) {
98        self.issues.extend(issues);
99    }
100
101    /// Flat string list of error messages (compat with legacy `CompatReport`).
102    pub fn error_messages(&self) -> Vec<String> {
103        self.issues
104            .iter()
105            .filter(|i| i.severity == ValidationSeverity::Error)
106            .map(|i| i.message.clone())
107            .collect()
108    }
109
110    pub fn issues_in_category(
111        &self,
112        category: ValidationCategory,
113    ) -> impl Iterator<Item = &PackageValidationIssue> {
114        self.issues
115            .iter()
116            .filter(move |i| i.category == category)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn counts_errors_and_warnings() {
126        let mut report = PackageValidationReport::default();
127        report.push(PackageValidationIssue::error(
128            ValidationCategory::Presentation,
129            "bad",
130            Some("ppt/presentation.xml"),
131        ));
132        report.push(PackageValidationIssue::warning(
133            ValidationCategory::Theme,
134            "soft",
135            None,
136        ));
137        assert_eq!(report.error_count(), 1);
138        assert_eq!(report.warning_count(), 1);
139        assert!(!report.is_valid());
140    }
141}