use headwater_graph::anchors::{Binding, Resolver};
pub const RESOLVER: &str = "check-rule";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rules {
held: &'static [&'static str],
}
impl Rules {
pub fn shipped() -> Self {
Rules {
held: &crate::RULES,
}
}
pub fn over(held: &'static [&'static str]) -> Self {
Rules { held }
}
pub fn held(&self) -> usize {
self.held.len()
}
}
impl Resolver for Rules {
fn name(&self) -> &str {
RESOLVER
}
fn resolve(&self, raw: &str) -> Binding {
let id = raw.trim();
if id.is_empty() {
return Binding::Unresolved("an empty anchor names nothing".to_string());
}
match self.held.contains(&id) {
true => Binding::Resolved {
matched: vec![id.to_string()],
normalized: id.to_string(),
excluded_by: None,
revision: None,
},
false => Binding::Unresolved(format!(
"this engine implements no rule `{id}`, and it ships {}",
match self.held.len() {
1 => "1 rule".to_string(),
other => format!("{other} rules"),
}
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const STATED: [&str; 2] = ["section.required.missing", "language.retired_term.used"];
fn over() -> Rules {
Rules::over(&STATED)
}
#[test]
fn a_rule_this_engine_ships_binds_to_the_string_it_ships() {
assert_eq!(
over().resolve("section.required.missing"),
Binding::Resolved {
matched: vec!["section.required.missing".to_string()],
normalized: "section.required.missing".to_string(),
excluded_by: None,
revision: None,
}
);
}
#[test]
fn it_trims_and_normalizes_nothing_else() {
assert_eq!(
over().resolve(" language.retired_term.used\n"),
Binding::Resolved {
matched: vec!["language.retired_term.used".to_string()],
normalized: "language.retired_term.used".to_string(),
excluded_by: None,
revision: None,
}
);
for spelling in [
"Section.Required.Missing",
"section_required_missing",
"check:section.required.missing",
"section.required",
] {
assert!(
matches!(over().resolve(spelling), Binding::Unresolved(_)),
"`{spelling}` is not the string this engine ships"
);
}
}
#[test]
fn a_refusal_names_the_identifier_and_says_how_many_rules_there_are() {
let Binding::Unresolved(why) = over().resolve("no.such.rule") else {
panic!("a rule this engine does not ship binds to nothing");
};
assert_eq!(
why,
"this engine implements no rule `no.such.rule`, and it ships 2 rules"
);
}
#[test]
fn an_empty_anchor_is_refused_on_its_own_terms() {
assert_eq!(
over().resolve(" "),
Binding::Unresolved("an empty anchor names nothing".to_string())
);
}
#[test]
fn it_answers_to_the_namespace_the_taxonomy_names() {
assert_eq!(over().name(), "check-rule");
assert_eq!(Rules::shipped().held(), crate::RULES.len());
}
#[test]
fn the_resolver_binds_exactly_what_the_register_calls_implemented() {
let resolver = Rules::shipped();
let mut checked = 0;
for rule in crate::RULES {
assert!(
matches!(resolver.resolve(rule), Binding::Resolved { .. }),
"the register calls `{rule}` implemented, so the resolver binds it"
);
checked += 1;
}
assert_eq!(checked, crate::RULES.len(), "every shipped rule was asked");
assert!(matches!(
resolver.resolve("no.such.rule"),
Binding::Unresolved(_)
));
}
}