diagnostics/
lib.rs

1//! Utilities for collecting diagnostics.
2
3#![warn(missing_docs)]
4
5#[cfg(test)]
6pub(crate) mod tests;
7
8use std::fmt::{Debug, Display};
9
10use serde::{Deserialize, Serialize};
11
12/// A diagnostic issue that should be reported to users.
13pub trait Diagnostic: Debug + Display {
14    /// Returns an optional help message that should indicate
15    /// what users need to do to resolve an issue.
16    fn help(&self) -> Option<Box<dyn Display>> {
17        None
18    }
19
20    /// Returns the severity of this issue.
21    ///
22    /// The default implementation returns [`Severity::default`].
23    fn severity(&self) -> Severity {
24        Default::default()
25    }
26}
27
28/// An enumeration of possible severity levels.
29#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
30pub enum Severity {
31    /// An informational message.
32    Info,
33    /// A warning.
34    #[default]
35    Warning,
36    /// An error. Often, but not always, fatal.
37    Error,
38}
39
40/// A collection of issues.
41#[derive(Debug, Clone)]
42pub struct IssueSet<T> {
43    issues: Vec<T>,
44    num_errors: usize,
45    num_warnings: usize,
46}
47
48impl<T> IssueSet<T> {
49    /// Creates a new, empty issue set.
50    #[inline]
51    pub fn new() -> Self {
52        Self {
53            issues: Vec::new(),
54            num_errors: 0,
55            num_warnings: 0,
56        }
57    }
58
59    /// Returns an iterator over all issues in the set.
60    #[inline]
61    pub fn iter(&self) -> impl Iterator<Item = &T> {
62        self.issues.iter()
63    }
64
65    /// The number of issues in this issue set.
66    #[inline]
67    pub fn len(&self) -> usize {
68        self.issues.len()
69    }
70
71    /// Returns `true` if this issue set is empty.
72    #[inline]
73    pub fn is_empty(&self) -> bool {
74        self.issues.is_empty()
75    }
76}
77
78impl<T: Diagnostic> IssueSet<T> {
79    /// Adds the given issue to the issue set.
80    #[inline]
81    pub fn add(&mut self, issue: T) {
82        let severity = issue.severity();
83        match severity {
84            Severity::Error => self.num_errors += 1,
85            Severity::Warning => self.num_warnings += 1,
86            _ => (),
87        };
88        self.issues.push(issue);
89    }
90
91    /// Returns `true` if this issue set contains an error.
92    ///
93    /// Errors are determined by [`Diagnostic`]s with a
94    /// (severity)[Diagnostic::severity] of [`Severity::Error`].
95    pub fn has_error(&self) -> bool {
96        self.num_errors > 0
97    }
98
99    /// The number of errors in this issue set.
100    #[inline]
101    pub fn num_errors(&self) -> usize {
102        self.num_errors
103    }
104
105    /// Returns `true` if this issue set contains a warning.
106    ///
107    /// Warnings are determined by [`Diagnostic`]s with a
108    /// (severity)[Diagnostic::severity] of [`Severity::Warning`].
109    pub fn has_warning(&self) -> bool {
110        self.num_warnings > 0
111    }
112
113    /// The number of warnings in this issue set.
114    #[inline]
115    pub fn num_warnings(&self) -> usize {
116        self.num_warnings
117    }
118}
119
120impl<T> IntoIterator for IssueSet<T> {
121    type Item = T;
122    type IntoIter = <std::vec::Vec<T> as IntoIterator>::IntoIter;
123    fn into_iter(self) -> Self::IntoIter {
124        self.issues.into_iter()
125    }
126}
127
128impl<T> Default for IssueSet<T> {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134impl Severity {
135    /// Returns log level corresponding to this severity.
136    #[inline]
137    pub const fn as_tracing_level(&self) -> tracing::Level {
138        match *self {
139            Self::Info => tracing::Level::INFO,
140            Self::Warning => tracing::Level::WARN,
141            Self::Error => tracing::Level::ERROR,
142        }
143    }
144
145    /// Returns `true` if the severity is [`Severity::Error`].
146    #[inline]
147    pub fn is_error(&self) -> bool {
148        matches!(*self, Self::Error)
149    }
150}
151
152impl Display for Severity {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        match *self {
155            Self::Info => write!(f, "info"),
156            Self::Warning => write!(f, "warning"),
157            Self::Error => write!(f, "error"),
158        }
159    }
160}
161
162impl<T: Display> Display for IssueSet<T> {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        for issue in self.issues.iter() {
165            writeln!(f, "{}", issue)?;
166        }
167        Ok(())
168    }
169}