use crate::{DiagnosticInfo, DiagnosticSeverity, StructuredDiagnostic, render_lines};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Diagnostics(Vec<StructuredDiagnostic>);
impl Diagnostics {
#[must_use]
pub fn new() -> Self {
Self(Vec::new())
}
pub fn push(&mut self, info: &DiagnosticInfo, message: impl Into<String>) {
self.0.push(StructuredDiagnostic::of(info, message));
}
pub fn push_at(
&mut self,
info: &DiagnosticInfo,
severity: DiagnosticSeverity,
message: impl Into<String>,
) {
self.0
.push(StructuredDiagnostic::of(info, message).with_severity(severity));
}
pub fn record(&mut self, diagnostic: StructuredDiagnostic) {
self.0.push(diagnostic);
}
pub fn absorb(&mut self, other: impl IntoIterator<Item = StructuredDiagnostic>) {
self.0.extend(other);
}
pub fn prepend(&mut self, other: impl IntoIterator<Item = StructuredDiagnostic>) {
let mut front: Vec<_> = other.into_iter().collect();
front.append(&mut self.0);
self.0 = front;
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn records(&self) -> &[StructuredDiagnostic] {
&self.0
}
#[must_use]
pub fn into_records(self) -> Vec<StructuredDiagnostic> {
self.0
}
#[must_use]
pub fn lines(&self) -> Vec<String> {
render_lines(&self.0)
}
#[must_use]
pub fn worst_severity(&self) -> Option<DiagnosticSeverity> {
self.0.iter().map(|d| d.severity).max()
}
}
impl From<Vec<StructuredDiagnostic>> for Diagnostics {
fn from(records: Vec<StructuredDiagnostic>) -> Self {
Self(records)
}
}
impl From<Diagnostics> for Vec<StructuredDiagnostic> {
fn from(diagnostics: Diagnostics) -> Self {
diagnostics.0
}
}
impl IntoIterator for Diagnostics {
type Item = StructuredDiagnostic;
type IntoIter = std::vec::IntoIter<StructuredDiagnostic>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
const DROPPED: DiagnosticInfo = DiagnosticInfo::new(
"EMIT.PSSE.FIELD_DROPPED",
DiagnosticSeverity::Warning,
"a field with no PSS/E record was dropped",
);
const REFUSED: DiagnosticInfo = DiagnosticInfo::new(
"READ.DSS.INCLUDE_REFUSED",
DiagnosticSeverity::Error,
"an include escaping the case directory was refused",
);
#[test]
fn a_site_takes_the_registered_severity_unless_it_names_one() {
let mut d = Diagnostics::new();
d.push(&DROPPED, "gencost dropped");
d.push_at(&DROPPED, DiagnosticSeverity::Info, "areas dropped");
assert_eq!(d.records()[0].severity, DiagnosticSeverity::Warning);
assert_eq!(d.records()[1].severity, DiagnosticSeverity::Info);
assert_eq!(d.worst_severity(), Some(DiagnosticSeverity::Warning));
}
#[test]
fn the_text_channel_is_rendered_from_the_records() {
let mut d = Diagnostics::new();
d.push(&DROPPED, "gencost dropped");
d.push(&REFUSED, "../shared.dss escapes the case directory");
assert_eq!(
d.lines(),
[
"EMIT.PSSE.FIELD_DROPPED: gencost dropped",
"READ.DSS.INCLUDE_REFUSED: ../shared.dss escapes the case directory",
]
);
}
#[test]
fn the_read_side_goes_ahead_of_the_write_side() {
let mut write = Diagnostics::new();
write.push(&DROPPED, "write");
let mut read = Diagnostics::new();
read.push(&REFUSED, "read");
write.prepend(read);
assert_eq!(
write.lines(),
[
"READ.DSS.INCLUDE_REFUSED: read",
"EMIT.PSSE.FIELD_DROPPED: write",
]
);
assert_eq!(write.len(), 2);
assert!(!write.is_empty());
}
}