use std::fmt;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Code {
Unreadable,
Envelope,
SchemaIdentity,
ShapeInvalid,
ShapeViolation,
MissingSection,
FragmentMalformed,
DuplicateId,
UnresolvedReference,
ReferenceKind,
UnresolvedLink,
ScriptLoad,
ScriptFailure,
ScriptResult,
PolicyError,
PolicyWarning,
ScriptOutput,
ScriptLint,
PlanInvalid,
OutputState,
Delivery,
}
impl Code {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Unreadable => "B001",
Self::Envelope => "B002",
Self::SchemaIdentity => "B003",
Self::ShapeInvalid => "B004",
Self::ShapeViolation => "B005",
Self::MissingSection => "B006",
Self::FragmentMalformed => "B007",
Self::DuplicateId => "B008",
Self::UnresolvedReference => "B009",
Self::ReferenceKind => "B010",
Self::UnresolvedLink => "B011",
Self::ScriptLoad => "B012",
Self::ScriptFailure => "B013",
Self::ScriptResult => "B014",
Self::PolicyError => "B015",
Self::PolicyWarning => "B016",
Self::ScriptOutput => "B017",
Self::ScriptLint => "B018",
Self::PlanInvalid => "B019",
Self::OutputState => "B020",
Self::Delivery => "B021",
}
}
#[must_use]
pub const fn severity(self) -> Severity {
match self {
Self::PolicyWarning | Self::ScriptOutput | Self::ScriptLint => Severity::Warning,
_ => Severity::Error,
}
}
pub const ALL: [Self; 21] = [
Self::Unreadable,
Self::Envelope,
Self::SchemaIdentity,
Self::ShapeInvalid,
Self::ShapeViolation,
Self::MissingSection,
Self::FragmentMalformed,
Self::DuplicateId,
Self::UnresolvedReference,
Self::ReferenceKind,
Self::UnresolvedLink,
Self::ScriptLoad,
Self::ScriptFailure,
Self::ScriptResult,
Self::PolicyError,
Self::PolicyWarning,
Self::ScriptOutput,
Self::ScriptLint,
Self::PlanInvalid,
Self::OutputState,
Self::Delivery,
];
}
impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for Code {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize)]
pub struct Diagnostic {
pub path: String,
pub code: Code,
pub line: Option<u32>,
pub rule: Option<String>,
pub severity: Severity,
pub message: String,
}
impl Diagnostic {
#[must_use]
pub fn new(code: Code, path: impl Into<String>, message: impl Into<String>) -> Self {
Self {
path: path.into(),
code,
line: None,
rule: None,
severity: code.severity(),
message: message.into(),
}
}
#[must_use]
pub fn at_line(mut self, line: Option<u32>) -> Self {
self.line = line;
self
}
#[must_use]
pub fn with_rule(mut self, rule: Option<String>) -> Self {
self.rule = rule;
self
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.path)?;
if let Some(line) = self.line {
write!(f, ":{line}")?;
}
write!(f, ":{}", self.code)?;
if let Some(rule) = &self.rule {
write!(f, "[{rule}]")?;
}
write!(f, ": {}", self.message)
}
}
#[derive(Debug, Default, Serialize)]
pub struct Report {
pub ok: bool,
pub resources: usize,
pub diagnostics: Vec<Diagnostic>,
pub outputs: Vec<String>,
pub fatal: Option<String>,
#[serde(skip)]
pub max_ticks: u64,
#[serde(skip)]
pub max_heap_bytes: u64,
#[serde(skip)]
pub max_fuel: u64,
#[serde(skip)]
pub max_output_bytes: u64,
}
impl Report {
#[must_use]
pub fn is_clean(&self) -> bool {
self.fatal.is_none() && self.errors() == 0
}
#[must_use]
pub fn errors(&self) -> usize {
self.diagnostics
.iter()
.filter(|diagnostic| diagnostic.severity == Severity::Error)
.count()
}
pub(crate) fn push(&mut self, diagnostic: Diagnostic) {
self.diagnostics.push(diagnostic);
}
pub(crate) fn extend(&mut self, diagnostics: impl IntoIterator<Item = Diagnostic>) {
self.diagnostics.extend(diagnostics);
}
pub(crate) fn finish(&mut self) {
self.diagnostics.sort();
self.diagnostics.dedup();
self.outputs.sort();
self.outputs.dedup();
self.ok = self.is_clean();
}
#[must_use]
pub fn fatal(message: impl Into<String>) -> Self {
Self {
ok: false,
fatal: Some(message.into()),
..Self::default()
}
}
}