use alloc::borrow::Cow;
use alloc::vec::Vec;
use crate::diagnostic::Diagnostic;
use crate::error::DriverError;
#[derive(Debug, Clone)]
pub struct Session<C> {
config: C,
diagnostics: Vec<Diagnostic>,
error_count: usize,
}
impl<C> Session<C> {
#[must_use]
pub fn new(config: C) -> Self {
Self {
config,
diagnostics: Vec::new(),
error_count: 0,
}
}
#[must_use]
#[inline]
pub fn config(&self) -> &C {
&self.config
}
#[must_use]
#[inline]
pub fn config_mut(&mut self) -> &mut C {
&mut self.config
}
#[must_use]
pub fn into_config(self) -> C {
self.config
}
pub fn emit(&mut self, diagnostic: Diagnostic) -> &mut Self {
if diagnostic.is_error() {
self.error_count = self.error_count.saturating_add(1);
}
self.diagnostics.push(diagnostic);
self
}
pub fn error(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
self.emit(Diagnostic::error(message))
}
pub fn warn(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
self.emit(Diagnostic::warning(message))
}
pub fn note(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
self.emit(Diagnostic::note(message))
}
#[must_use]
#[inline]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
#[must_use]
#[inline]
pub fn error_count(&self) -> usize {
self.error_count
}
#[must_use]
#[inline]
pub fn has_errors(&self) -> bool {
self.error_count != 0
}
pub fn abort_if_errors(&self) -> Result<(), DriverError> {
if self.error_count == 0 {
return Ok(());
}
let noun = if self.error_count == 1 {
"error"
} else {
"errors"
};
Err(DriverError::new(alloc::format!(
"aborting due to {} previous {noun}",
self.error_count
)))
}
#[must_use]
pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
self.error_count = 0;
core::mem::take(&mut self.diagnostics)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::severity::Severity;
use alloc::vec::Vec;
#[test]
fn test_new_session_is_clean() {
let session = Session::new(());
assert!(!session.has_errors());
assert_eq!(session.error_count(), 0);
assert!(session.diagnostics().is_empty());
}
#[test]
fn test_config_accessors() {
let mut session = Session::new(1u32);
assert_eq!(*session.config(), 1);
*session.config_mut() = 2;
assert_eq!(*session.config(), 2);
assert_eq!(session.into_config(), 2);
}
#[test]
fn test_emit_counts_only_errors() {
let mut session = Session::new(());
session
.emit(Diagnostic::error("e"))
.emit(Diagnostic::warning("w"))
.emit(Diagnostic::note("n"));
assert_eq!(session.diagnostics().len(), 3);
assert_eq!(session.error_count(), 1);
}
#[test]
fn test_shorthands_set_expected_severity() {
let mut session = Session::new(());
session.error("e").warn("w").note("n");
let severities: Vec<_> = session.diagnostics().iter().map(|d| d.severity()).collect();
assert_eq!(
severities,
[Severity::Error, Severity::Warning, Severity::Note]
);
}
#[test]
fn test_diagnostics_preserve_emission_order() {
let mut session = Session::new(());
session.note("a").error("b").warn("c");
let messages: Vec<_> = session.diagnostics().iter().map(|d| d.message()).collect();
assert_eq!(messages, ["a", "b", "c"]);
}
#[test]
fn test_abort_if_errors_ok_when_clean() {
let mut session = Session::new(());
session.warn("only a warning");
assert!(session.abort_if_errors().is_ok());
}
#[test]
fn test_abort_if_errors_singular_message() {
let mut session = Session::new(());
session.error("one");
let err = session.abort_if_errors().unwrap_err();
assert_eq!(err.message(), "aborting due to 1 previous error");
}
#[test]
fn test_abort_if_errors_plural_message() {
let mut session = Session::new(());
session.error("one").error("two").error("three");
let err = session.abort_if_errors().unwrap_err();
assert_eq!(err.message(), "aborting due to 3 previous errors");
}
#[test]
fn test_take_diagnostics_drains_and_resets() {
let mut session = Session::new(());
session.error("a").warn("b");
let drained = session.take_diagnostics();
assert_eq!(drained.len(), 2);
assert!(!session.has_errors());
assert_eq!(session.error_count(), 0);
assert!(session.diagnostics().is_empty());
}
}