use crate::finding::{at, Finding, Severity};
use crate::instance::Outcome;
use crate::scope::{DocumentCheck, DocumentView};
use crate::shape::Shape;
use headwater_meta::identifier::{Template, Unreadable};
pub const RULE: &str = "identifier.pattern.not_met";
pub struct Identifier {
schemes: Vec<(String, String, Result<Template, Unreadable>)>,
facet: String,
}
impl Identifier {
pub fn over(shape: &Shape, facet: &str) -> Self {
Identifier {
schemes: shape
.kinds
.iter()
.filter_map(|kind| {
let scheme = shape.identifier_scheme_of(&kind.name)?;
Some((
kind.name.clone(),
scheme.name.clone(),
Template::parse(&scheme.pattern, &scheme.namespace),
))
})
.collect(),
facet: facet.to_string(),
}
}
fn scheme_of(&self, kind: &str) -> Option<&(String, String, Result<Template, Unreadable>)> {
self.schemes.iter().find(|(known, _, _)| known == kind)
}
}
impl DocumentCheck for Identifier {
const RULE: &'static str = self::RULE;
const VERSION: u32 = 1;
fn instantiates(&self, kind: &str) -> bool {
self.scheme_of(kind).is_some()
}
fn evaluate(&self, view: &DocumentView<'_>) -> Outcome {
let Some((_, name, template)) = self.scheme_of(view.kind()) else {
return Outcome::Passed;
};
let template = match template {
Ok(template) => template,
Err(Unreadable(reason)) => {
return Outcome::Skipped(format!("scheme `{name}`: {reason}"))
}
};
let facet = &self.facet;
let Some(entry) = view.facets().get(facet) else {
return Outcome::Skipped(format!(
"no `{facet}` facet, which is the key this engine reads an identifier from and which no declaration states"
));
};
let Some(scalar) = entry.value.as_scalar() else {
return Outcome::Skipped(format!(
"the `{facet}` facet is {}, and an identifier is a word",
entry.value.kind_name()
));
};
let identifier = scalar.text.trim();
let Some(reason) = template.refusal(identifier) else {
return Outcome::Passed;
};
let (line, column) = at(view.facets().key_span(facet));
Outcome::failed_with(Finding {
rule: self::RULE,
severity: Severity::Error,
obligation: None,
path: view.path().to_string(),
line,
column,
message: format!(
"`{identifier}` does not match `{}`, which scheme `{name}` declares: {reason}",
template.render()
),
remediation: format!(
"write the identifier of {} in the form `{}`",
view.path(),
template.render()
),
patch: None,
})
}
}