1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Utilities for collecting diagnostics.

#![warn(missing_docs)]

#[cfg(test)]
pub(crate) mod tests;

use std::fmt::{Debug, Display};

use serde::{Deserialize, Serialize};

/// A diagnostic issue that should be reported to users.
pub trait Diagnostic: Debug + Display {
    /// Returns an optional help message that should indicate
    /// what users need to do to resolve an issue.
    fn help(&self) -> Option<Box<dyn Display>> {
        None
    }

    /// Returns the severity of this issue.
    ///
    /// The default implementation returns [`Severity::default`].
    fn severity(&self) -> Severity {
        Default::default()
    }
}

/// An enumeration of possible severity levels.
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum Severity {
    /// An informational message.
    Info,
    /// A warning.
    #[default]
    Warning,
    /// An error. Often, but not always, fatal.
    Error,
}

/// A collection of issues.
#[derive(Debug, Clone)]
pub struct IssueSet<T> {
    issues: Vec<T>,
    num_errors: usize,
    num_warnings: usize,
}

impl<T> IssueSet<T> {
    /// Creates a new, empty issue set.
    #[inline]
    pub fn new() -> Self {
        Self {
            issues: Vec::new(),
            num_errors: 0,
            num_warnings: 0,
        }
    }

    /// Returns an iterator over all issues in the set.
    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.issues.iter()
    }

    /// The number of issues in this issue set.
    #[inline]
    pub fn len(&self) -> usize {
        self.issues.len()
    }

    /// Returns `true` if this issue set is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.issues.is_empty()
    }
}

impl<T: Diagnostic> IssueSet<T> {
    /// Adds the given issue to the issue set.
    #[inline]
    pub fn add(&mut self, issue: T) {
        let severity = issue.severity();
        match severity {
            Severity::Error => self.num_errors += 1,
            Severity::Warning => self.num_warnings += 1,
            _ => (),
        };
        self.issues.push(issue);
    }

    /// Returns `true` if this issue set contains an error.
    ///
    /// Errors are determined by [`Diagnostic`]s with a
    /// (severity)[Diagnostic::severity] of [`Severity::Error`].
    pub fn has_error(&self) -> bool {
        self.num_errors > 0
    }

    /// The number of errors in this issue set.
    #[inline]
    pub fn num_errors(&self) -> usize {
        self.num_errors
    }

    /// Returns `true` if this issue set contains a warning.
    ///
    /// Warnings are determined by [`Diagnostic`]s with a
    /// (severity)[Diagnostic::severity] of [`Severity::Warning`].
    pub fn has_warning(&self) -> bool {
        self.num_warnings > 0
    }

    /// The number of warnings in this issue set.
    #[inline]
    pub fn num_warnings(&self) -> usize {
        self.num_warnings
    }
}

impl<T> IntoIterator for IssueSet<T> {
    type Item = T;
    type IntoIter = <std::vec::Vec<T> as IntoIterator>::IntoIter;
    fn into_iter(self) -> Self::IntoIter {
        self.issues.into_iter()
    }
}

impl<T> Default for IssueSet<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl Severity {
    /// Returns log level corresponding to this severity.
    #[inline]
    pub const fn as_tracing_level(&self) -> tracing::Level {
        match *self {
            Self::Info => tracing::Level::INFO,
            Self::Warning => tracing::Level::WARN,
            Self::Error => tracing::Level::ERROR,
        }
    }

    /// Returns `true` if the severity is [`Severity::Error`].
    #[inline]
    pub fn is_error(&self) -> bool {
        matches!(*self, Self::Error)
    }
}

impl Display for Severity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Self::Info => write!(f, "info"),
            Self::Warning => write!(f, "warning"),
            Self::Error => write!(f, "error"),
        }
    }
}

impl<T: Display> Display for IssueSet<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for issue in self.issues.iter() {
            writeln!(f, "{}", issue)?;
        }
        Ok(())
    }
}