Skip to main content

molframe_core/diagnostic/
findings.rs

1//! A finished set of findings, as the error an operation returns.
2
3use super::code::Code;
4use super::finding::Diagnostic;
5use std::fmt;
6
7/// The findings an operation produced, as a finished set.
8///
9/// [`Diagnostics`](super::Diagnostics) accumulates and
10/// [`Diagnostics::finish`](super::Diagnostics::finish) imposes the order; this is
11/// what comes out the other side, and it is an error because reaching it means the
12/// operation did not produce what was asked for. The findings say why, in the
13/// order two runs agree on.
14///
15/// It derefs to the findings, so a caller that wants the list takes it as a
16/// slice; `for finding in &set` iterates without naming [`Deref`] at all.
17///
18/// # Examples
19///
20/// ```
21/// use molframe_core::{Code, Diagnostic, Findings};
22///
23/// let findings = Findings::from(Diagnostic::new(Code::E1001));
24/// assert_eq!(findings.len(), 1);
25/// assert_eq!(findings.code(), Some(Code::E1001));
26///
27/// // The order `Diagnostics::finish` produces, and a report that reads it.
28/// println!("{findings}");
29/// ```
30///
31/// [`Deref`]: std::ops::Deref
32#[derive(Clone, Debug)]
33pub struct Findings(Vec<Diagnostic>);
34
35impl Findings {
36    /// The code of the finding that stopped the operation, if any.
37    ///
38    /// A stable identity to branch on, for a caller that would rather not match
39    /// on message text. `None` when the set is empty — a set that stopped
40    /// nothing rather than a set that failed to say why.
41    #[must_use]
42    pub fn code(&self) -> Option<Code> {
43        self.0.first().map(Diagnostic::code)
44    }
45
46    /// The findings.
47    ///
48    /// Declared rather than inherited from [`Deref`], whose `[Diagnostic]` target
49    /// reaches `[T]::as_slice` — an unstable inherent method that shadows a stable
50    /// reading of the same call. [`Diagnostics`](super::Diagnostics) names it the
51    /// same way.
52    ///
53    /// [`Deref`]: std::ops::Deref
54    #[must_use]
55    pub fn as_slice(&self) -> &[Diagnostic] {
56        &self.0
57    }
58
59    /// Consumes the set, returning the findings.
60    #[must_use]
61    pub fn into_vec(self) -> Vec<Diagnostic> {
62        self.0
63    }
64}
65
66impl std::ops::Deref for Findings {
67    type Target = [Diagnostic];
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74impl<'a> IntoIterator for &'a Findings {
75    type Item = &'a Diagnostic;
76    type IntoIter = std::slice::Iter<'a, Diagnostic>;
77
78    fn into_iter(self) -> Self::IntoIter {
79        self.0.iter()
80    }
81}
82
83/// Prints the first finding and how many follow it.
84///
85/// A set can hold thousands, and an error message that renders all of them is
86/// one nobody reads. The first is the worst by
87/// [`Diagnostics::finish`](super::Diagnostics::finish)'s order, which is the one
88/// worth naming.
89impl fmt::Display for Findings {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self.0.as_slice() {
92            [] => f.write_str("no findings"),
93            [only] => write!(f, "{only}"),
94            [first, rest @ ..] => write!(f, "{first} (and {} more)", rest.len()),
95        }
96    }
97}
98
99impl std::error::Error for Findings {}
100
101impl From<Diagnostic> for Findings {
102    fn from(finding: Diagnostic) -> Self {
103        Self(vec![finding])
104    }
105}
106
107impl From<Vec<Diagnostic>> for Findings {
108    fn from(findings: Vec<Diagnostic>) -> Self {
109        Self(findings)
110    }
111}
112
113impl From<Findings> for Vec<Diagnostic> {
114    fn from(findings: Findings) -> Self {
115        findings.0
116    }
117}
118
119#[cfg(test)]
120#[path = "findings_tests.rs"]
121mod tests;