core-invoice 2.0.0

EN 16931 + Peppol PINT semantic model (VAT, GST, SST, consumption tax)
Documentation
use crate::bt::Path;
use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
    Fatal,
    Warning,
    Info,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Source {
    Both,
    StandardOnly,
    ArtefactOnly,
    Crate,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Finding {
    pub id: &'static str,
    pub severity: Severity,
    pub path: Path,
    pub message: String,
    pub detail: Option<String>,
    pub hint: Option<String>,
}

impl Finding {
    pub fn fatal(id: &'static str, path: Path, message: impl Into<String>) -> Self {
        Self {
            id,
            severity: Severity::Fatal,
            path,
            message: message.into(),
            detail: None,
            hint: None,
        }
    }

    pub fn warning(id: &'static str, path: Path, message: impl Into<String>) -> Self {
        Self {
            id,
            severity: Severity::Warning,
            path,
            message: message.into(),
            detail: None,
            hint: None,
        }
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Report {
    pub findings: Vec<Finding>,
    pub profile_slug: &'static str,
    pub rules_checked: usize,
}

impl Report {
    pub fn ok(&self) -> bool {
        !self.findings.iter().any(|f| f.severity == Severity::Fatal)
    }

    pub fn push(&mut self, finding: Finding) {
        self.findings.push(finding);
    }

    pub fn sort_stable(&mut self) {
        self.findings.sort_by(|a, b| {
            a.severity
                .cmp(&b.severity)
                .then_with(|| a.path.to_string().cmp(&b.path.to_string()))
                .then_with(|| a.id.cmp(b.id))
        });
    }
}

impl fmt::Display for Finding {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}] {}{}", self.id, self.path, self.message)?;
        if let Some(d) = &self.detail {
            write!(f, " ({d})")?;
        }
        Ok(())
    }
}

impl fmt::Display for Report {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.ok() {
            return write!(f, "valid");
        }
        for finding in &self.findings {
            writeln!(f, "{finding}")?;
        }
        Ok(())
    }
}