use std::{fmt, sync::Arc};
#[derive(Debug, Default)]
pub struct Validation<D> {
errors: Vec<D>,
warnings: Vec<D>,
}
impl<D> Validation<D> {
pub fn new() -> Self {
Self {
errors: vec![],
warnings: vec![],
}
}
pub fn with_warning(&mut self, warning: D) {
self.warnings.push(warning)
}
pub fn with_error(&mut self, error: D) {
self.errors.push(error)
}
pub fn with_warnings(&mut self, warnings: impl IntoIterator<Item = D>) {
self.warnings.extend(warnings)
}
pub fn with_errors(&mut self, errors: impl IntoIterator<Item = D>) {
self.errors.extend(errors)
}
pub fn append(&mut self, diags: impl IntoIterator<Item = D>)
where
D: Diagnostic,
{
let (errors, warnings): (Vec<_>, Vec<_>) = diags.into_iter().partition(D::is_error);
self.with_warnings(warnings);
self.with_errors(errors);
}
pub fn append_from_result<C>(&mut self, result: Result<Vec<D>, Vec<D>>, context: &C)
where
D: Diagnostic,
C: std::fmt::Display + ?Sized,
{
match result {
Ok(warnings) => self.append(warnings.into_iter().map(|w| w.contextualize(context))),
Err(errors) => self.append(errors.into_iter().map(|e| e.contextualize(context))),
}
}
}
impl<D> From<Validation<D>> for Result<Vec<D>, Vec<D>> {
fn from(mut value: Validation<D>) -> Self {
if value.errors.is_empty() {
Ok(value.warnings)
} else {
value.errors.extend(value.warnings);
Err(value.errors)
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DiagnosticKind {
Warning,
Error,
Other,
}
pub trait Diagnostic: fmt::Display {
fn kind(&self) -> DiagnosticKind;
fn is_error(&self) -> bool {
self.kind() == DiagnosticKind::Error
}
fn contextualize<C>(self, context: &C) -> Self
where
C: ToString + ?Sized,
Self: Sized;
}
#[derive(Debug, Clone)]
pub struct SimpleDiagnostic {
kind: DiagnosticKind,
message: String,
}
impl SimpleDiagnostic {
pub fn warn(message: impl ToString) -> Self {
Self {
kind: DiagnosticKind::Warning,
message: message.to_string(),
}
}
pub fn error(message: impl ToString) -> Self {
Self {
kind: DiagnosticKind::Error,
message: message.to_string(),
}
}
pub fn other(message: impl ToString) -> Self {
Self {
kind: DiagnosticKind::Other,
message: message.to_string(),
}
}
}
impl Diagnostic for SimpleDiagnostic {
fn kind(&self) -> DiagnosticKind {
self.kind
}
fn contextualize<C>(self, context: &C) -> Self
where
C: ToString + ?Sized,
{
let mut final_message = context.to_string();
if final_message.is_empty() {
return self;
}
final_message.reserve(2 + self.message.len());
final_message.push_str(": ");
final_message.push_str(&self.message);
Self {
kind: self.kind,
message: final_message,
}
}
}
impl fmt::Display for SimpleDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {}",
match self.kind {
DiagnosticKind::Warning => "warning",
DiagnosticKind::Error => "error",
DiagnosticKind::Other => "other",
},
self.message
)
}
}
pub struct DiagnosticsError {
diags: Vec<Arc<dyn Diagnostic + Send + Sync + 'static>>,
}
impl std::fmt::Debug for DiagnosticsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DiagnosticsError")
.field("n_diags", &self.diags.len())
.finish()
}
}
impl std::fmt::Display for DiagnosticsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.diags
.iter()
.map(AsRef::as_ref)
.try_for_each(|diag| writeln!(f, "- {diag}\n"))
}
}
impl<'d, D: Diagnostic + Send + Sync + 'static> FromIterator<D> for DiagnosticsError {
fn from_iter<T: IntoIterator<Item = D>>(iter: T) -> Self {
Self {
diags: iter
.into_iter()
.map(|d| -> Arc<dyn Diagnostic + Send + Sync + 'static> { Arc::new(d) })
.collect(),
}
}
}
impl<I: IntoIterator> From<I> for DiagnosticsError
where
I::Item: Diagnostic + Send + Sync + 'static,
{
fn from(value: I) -> Self {
Self::from_iter(value)
}
}