use crate::finding::{Finding, Severity};
use crate::instance::Outcome;
use crate::scope::{DocumentCheck, DocumentView, ExportTargets};
use crate::shape::Shape;
pub const RULE: &str = "facet.required.missing";
pub struct Required {
owed: Vec<(String, Vec<String>)>,
}
impl Required {
pub fn over(shape: &Shape) -> Self {
Required {
owed: shape
.kinds
.iter()
.map(|kind| (kind.name.clone(), shape.required_facets(&kind.name)))
.filter(|(_, facets)| !facets.is_empty())
.collect(),
}
}
fn owed_by(&self, kind: &str) -> &[String] {
self.owed
.iter()
.find(|(known, _)| known == kind)
.map(|(_, facets)| facets.as_slice())
.unwrap_or_default()
}
}
impl DocumentCheck for Required {
const RULE: &'static str = self::RULE;
const VERSION: u32 = 1;
const EXPORTABLE_AS: ExportTargets = &["jsonschema"];
fn instantiates(&self, kind: &str) -> bool {
!self.owed_by(kind).is_empty()
}
fn evaluate(&self, view: &DocumentView<'_>) -> Outcome {
let kind = view.kind();
let findings = self
.owed_by(kind)
.iter()
.filter(|facet| view.facets().entry(facet).is_none())
.map(|facet| Finding {
rule: self::RULE,
severity: Severity::Error,
obligation: None,
path: view.path().to_string(),
line: 0,
column: 0,
message: format!("`{kind}` requires the facet `{facet}`, and it is not declared"),
remediation: format!(
"add `{facet}:` to the front matter of {}, with the value this document has",
view.path()
),
patch: None,
})
.collect();
Outcome::failed(findings)
}
}