ass_core/parser/errors/
parse_result.rs

1//! Parse result types for error handling and issue collection
2//!
3//! Provides result types that can carry both successful parsing results
4//! and accumulated issues/warnings. Enables partial recovery parsing
5//! where some errors can be worked around while still collecting problems.
6
7use alloc::vec::Vec;
8
9use super::{
10    parse_error::ParseError,
11    parse_issue::{IssueSeverity, ParseIssue},
12};
13
14/// Result type for operations that can produce parse issues
15///
16/// Standard Result type using `ParseError` for the error case.
17/// Use `ParseResultWithIssues` for operations that need to collect
18/// warnings and recoverable errors alongside the main result.
19pub type ParseResult<T> = Result<T, ParseError>;
20
21/// Parse result with accumulated issues for partial recovery
22///
23/// Allows parsing to continue even when encountering recoverable errors,
24/// collecting issues for later review while still producing a usable result.
25/// Essential for editor integration and robust script processing.
26#[derive(Debug, Clone)]
27pub struct ParseResultWithIssues<T> {
28    /// The parsed result (if successful)
29    pub result: ParseResult<T>,
30
31    /// Accumulated parse issues from warnings to recoverable errors
32    pub issues: Vec<ParseIssue>,
33}
34
35impl<T> ParseResultWithIssues<T> {
36    /// Create successful result with no issues
37    ///
38    /// # Arguments
39    ///
40    /// * `value` - The successfully parsed value
41    ///
42    /// # Returns
43    ///
44    /// Result containing the value with empty issues list
45    pub const fn ok(value: T) -> Self {
46        Self {
47            result: Ok(value),
48            issues: Vec::new(),
49        }
50    }
51
52    /// Create error result with no issues
53    ///
54    /// # Arguments
55    ///
56    /// * `error` - The parse error that occurred
57    ///
58    /// # Returns
59    ///
60    /// Result containing the error with empty issues list
61    #[must_use]
62    pub const fn err(error: ParseError) -> Self {
63        Self {
64            result: Err(error),
65            issues: Vec::new(),
66        }
67    }
68
69    /// Create result with pre-collected issues
70    ///
71    /// # Arguments
72    ///
73    /// * `result` - The parse result (success or failure)
74    /// * `issues` - List of issues encountered during parsing
75    ///
76    /// # Returns
77    ///
78    /// Result with the provided issues attached
79    pub const fn with_issues(result: ParseResult<T>, issues: Vec<ParseIssue>) -> Self {
80        Self { result, issues }
81    }
82
83    /// Add issue to existing result
84    ///
85    /// # Arguments
86    ///
87    /// * `issue` - Parse issue to add to the collection
88    ///
89    /// # Returns
90    ///
91    /// Self with the issue added to the issues list
92    #[must_use]
93    pub fn add_issue(mut self, issue: ParseIssue) -> Self {
94        self.issues.push(issue);
95        self
96    }
97
98    /// Get only critical issues from the collection
99    ///
100    /// Critical issues indicate serious problems that will likely
101    /// affect rendering or script functionality.
102    ///
103    /// # Returns
104    ///
105    /// Vector of references to critical issues only
106    pub fn critical_issues(&self) -> Vec<&ParseIssue> {
107        self.issues
108            .iter()
109            .filter(|issue| matches!(issue.severity, IssueSeverity::Critical))
110            .collect()
111    }
112
113    /// Check if result has any blocking issues
114    ///
115    /// Blocking issues are those that should prevent further processing
116    /// or indicate fundamental problems with the script.
117    ///
118    /// # Returns
119    ///
120    /// True if any issue is marked as blocking
121    pub fn has_blocking_issues(&self) -> bool {
122        self.issues.iter().any(ParseIssue::is_blocking)
123    }
124
125    /// Get issue count by severity level
126    ///
127    /// # Arguments
128    ///
129    /// * `severity` - The severity level to count
130    ///
131    /// # Returns
132    ///
133    /// Number of issues with the specified severity
134    pub fn count_by_severity(&self, severity: IssueSeverity) -> usize {
135        self.issues
136            .iter()
137            .filter(|issue| issue.severity == severity)
138            .count()
139    }
140
141    /// Check if parsing was successful (ignoring issues)
142    ///
143    /// # Returns
144    ///
145    /// True if the main result is Ok, regardless of issues
146    pub const fn is_ok(&self) -> bool {
147        self.result.is_ok()
148    }
149
150    /// Check if parsing failed with an error
151    ///
152    /// # Returns
153    ///
154    /// True if the main result is Err
155    pub const fn is_err(&self) -> bool {
156        self.result.is_err()
157    }
158}
159
160impl<T> From<ParseResult<T>> for ParseResultWithIssues<T> {
161    /// Convert a simple `ParseResult` into a `ParseResultWithIssues`
162    ///
163    /// Creates a result with no accumulated issues.
164    fn from(result: ParseResult<T>) -> Self {
165        Self {
166            result,
167            issues: Vec::new(),
168        }
169    }
170}
171
172impl<T> From<T> for ParseResultWithIssues<T> {
173    /// Convert a value into a successful `ParseResultWithIssues`
174    ///
175    /// Creates an Ok result with no accumulated issues.
176    fn from(value: T) -> Self {
177        Self::ok(value)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use crate::parser::errors::{IssueCategory, IssueSeverity, ParseError, ParseIssue};
185    #[cfg(not(feature = "std"))]
186    use alloc::{
187        string::{String, ToString},
188        vec,
189    };
190
191    #[test]
192    fn parse_result_with_issues_ok() {
193        let result = ParseResultWithIssues::ok(42);
194        assert!(result.is_ok());
195        assert!(!result.is_err());
196        assert_eq!(result.issues.len(), 0);
197        assert!(!result.has_blocking_issues());
198    }
199
200    #[test]
201    fn parse_result_with_issues_err() {
202        let error = ParseError::ExpectedSectionHeader { line: 5 };
203        let result = ParseResultWithIssues::<i32>::err(error);
204        assert!(result.is_err());
205        assert!(!result.is_ok());
206        assert_eq!(result.issues.len(), 0);
207    }
208
209    #[test]
210    fn parse_result_with_issues_add_issue() {
211        let mut result = ParseResultWithIssues::ok(42);
212        let issue = ParseIssue::warning(IssueCategory::Format, "Minor issue".to_string(), 1);
213        result = result.add_issue(issue);
214
215        assert!(result.is_ok());
216        assert_eq!(result.issues.len(), 1);
217        assert!(!result.has_blocking_issues());
218        assert_eq!(result.count_by_severity(IssueSeverity::Warning), 1);
219        assert_eq!(result.count_by_severity(IssueSeverity::Critical), 0);
220    }
221
222    #[test]
223    fn parse_result_with_issues_blocking() {
224        let mut result = ParseResultWithIssues::ok(42);
225        let critical_issue =
226            ParseIssue::critical(IssueCategory::Structure, "Critical error".to_string(), 1);
227        result = result.add_issue(critical_issue);
228
229        assert!(result.has_blocking_issues());
230        assert_eq!(result.critical_issues().len(), 1);
231        assert_eq!(result.count_by_severity(IssueSeverity::Critical), 1);
232    }
233
234    #[test]
235    fn parse_result_with_issues_multiple_severities() {
236        let mut result = ParseResultWithIssues::ok("test");
237
238        result = result.add_issue(ParseIssue::info(
239            IssueCategory::Performance,
240            "Info message".to_string(),
241            1,
242        ));
243
244        result = result.add_issue(ParseIssue::warning(
245            IssueCategory::Style,
246            "Warning message".to_string(),
247            2,
248        ));
249
250        result = result.add_issue(ParseIssue::error(
251            IssueCategory::Color,
252            "Error message".to_string(),
253            3,
254        ));
255
256        assert_eq!(result.issues.len(), 3);
257        assert_eq!(result.count_by_severity(IssueSeverity::Info), 1);
258        assert_eq!(result.count_by_severity(IssueSeverity::Warning), 1);
259        assert_eq!(result.count_by_severity(IssueSeverity::Error), 1);
260        assert_eq!(result.count_by_severity(IssueSeverity::Critical), 0);
261        assert!(!result.has_blocking_issues());
262    }
263
264    #[test]
265    fn parse_result_with_issues_from_parse_result() {
266        let parse_result: ParseResult<i32> = Ok(100);
267        let result_with_issues: ParseResultWithIssues<i32> =
268            ParseResultWithIssues::from(parse_result);
269
270        assert!(result_with_issues.is_ok());
271        assert_eq!(result_with_issues.issues.len(), 0);
272    }
273
274    #[test]
275    fn parse_result_with_issues_from_value() {
276        let result_with_issues = ParseResultWithIssues::from("hello");
277
278        assert!(result_with_issues.is_ok());
279        assert_eq!(result_with_issues.issues.len(), 0);
280        if let Ok(value) = result_with_issues.result {
281            assert_eq!(value, "hello");
282        }
283    }
284
285    #[test]
286    fn parse_result_with_issues_from_error() {
287        let error = ParseError::InvalidFieldFormat { line: 10 };
288        let parse_result: ParseResult<String> = Err(error);
289        let result_with_issues: ParseResultWithIssues<String> =
290            ParseResultWithIssues::from(parse_result);
291
292        assert!(result_with_issues.is_err());
293        assert_eq!(result_with_issues.issues.len(), 0);
294    }
295
296    #[test]
297    fn parse_result_with_issues_pre_collected() {
298        let issues = vec![
299            ParseIssue::warning(IssueCategory::Timing, "Late timing".to_string(), 5),
300            ParseIssue::error(IssueCategory::Font, "Missing font".to_string(), 10),
301        ];
302
303        let result = ParseResultWithIssues::with_issues(Ok("success"), issues);
304
305        assert!(result.is_ok());
306        assert_eq!(result.issues.len(), 2);
307        assert_eq!(result.count_by_severity(IssueSeverity::Warning), 1);
308        assert_eq!(result.count_by_severity(IssueSeverity::Error), 1);
309    }
310}