use crate::finding::{at, Finding, Severity};
use crate::instance::Outcome;
use crate::scope::{DocumentCheck, DocumentView};
use headwater_census::shelves::{ShelfBody, Taxonomy};
pub const RULE: &str = "shelf.placement_is_primary";
const HETEROGENEOUS: &str =
"the shelf is heterogeneous, so the discriminator is the gap metadata fills, \
and kind resolution already read it";
pub struct Placement {
discriminators: Vec<String>,
}
impl Placement {
pub fn over(taxonomy: &Taxonomy) -> Self {
let mut discriminators: Vec<String> = Vec::new();
for shelf in &taxonomy.shelves {
let ShelfBody::Heterogeneous { discriminator, .. } = &shelf.body else {
continue;
};
if !discriminators.contains(discriminator) {
discriminators.push(discriminator.clone());
}
}
Placement { discriminators }
}
}
impl DocumentCheck for Placement {
const RULE: &'static str = self::RULE;
const VERSION: u32 = 2;
fn evaluate(&self, view: &DocumentView<'_>) -> Outcome {
let Some(shelf) = view.placed_on() else {
return Outcome::Skipped(HETEROGENEOUS.to_string());
};
let restated = std::iter::once("kind")
.chain(self.discriminators.iter().map(String::as_str))
.find_map(|facet| view.facets().entry(facet).map(|entry| (facet, entry)));
let Some((facet, entry)) = restated else {
return Outcome::Passed;
};
let (line, column) = at(Some(entry.key.span));
let value = entry
.value
.value
.as_scalar()
.map(|scalar| scalar.text.clone())
.unwrap_or_else(|| entry.value.value.kind_name().to_string());
let kind = view.kind();
Outcome::failed_with(Finding {
rule: self::RULE,
severity: Severity::Error,
obligation: None,
path: view.path().to_string(),
line,
column,
message: format!(
"`{shelf}` is homogeneous and carries the kind `{kind}`, \
and this document restates it as `{facet}: {value}`"
),
remediation: format!("remove `{facet}` from the front matter of {}", view.path()),
patch: None,
})
}
}