use super::error::{Located, Problem};
use super::record::Rec;
use super::schema::Kind;
use super::schema::field;
use crate::attributes::BandKind;
use crate::decision::{DriverKind, StateKind};
use crate::statement::Form;
fn variant(kind: Kind, value: &str) -> &'static [&'static str] {
match kind {
Kind::Statement => Form::parse(value).map_or(&[], Form::slots),
Kind::State => StateKind::parse(value).map_or(&[], |state| match state {
StateKind::Proposed => &[],
StateKind::Accepted => &[field::SELECTED_OPTION, field::OUTCOME],
StateKind::Rejected => &[field::RATIONALE],
StateKind::Superseded => &[field::SELECTED_OPTION, field::OUTCOME, field::SUCCESSOR],
StateKind::Retired => &[field::SELECTED_OPTION, field::OUTCOME, field::RATIONALE],
}),
Kind::Driver => DriverKind::parse(value).map_or(&[], |driver| match driver {
DriverKind::Local => &[field::LABEL, field::STATEMENT],
DriverKind::Subject => &[field::SUBJECT],
}),
Kind::Band => BandKind::parse(value).map_or(&[], |band| match band {
BandKind::AtMost => &[field::UPPER],
BandKind::AtLeast => &[field::LOWER],
BandKind::Exactly => &[field::VALUE],
BandKind::Between => &[field::LOWER, field::UPPER],
}),
_ => &[],
}
}
const fn discriminant(kind: Kind) -> Option<(&'static str, &'static [&'static str])> {
match kind {
Kind::Statement => Some((field::FORM, &[])),
Kind::State | Kind::Band => Some((field::KIND, &[])),
Kind::Driver => Some((field::KIND, &[field::KEY])),
_ => None,
}
}
pub(crate) fn check(record: &Rec) -> Result<(), Located> {
let Some((selector, common)) = discriminant(record.kind) else {
return Ok(());
};
let shape = |problem: String| Located {
position: record.position,
problem: Problem::Shape(problem),
};
if record.kind == Kind::Statement && record.get(field::TEXT).is_some() {
return match record.fields.len() {
1 => Ok(()),
_ => Err(shape(
"a statement with `text` holds no other field".to_owned(),
)),
};
}
let Some(value) = record.text(selector) else {
return Err(Located {
position: record.position,
problem: Problem::MissingField(if record.kind == Kind::Statement {
field::TEXT
} else {
selector
}),
});
};
let takes = variant(record.kind, value);
for (name, _) in &record.fields {
if *name != selector && !common.contains(name) && !takes.contains(name) {
return Err(shape(format!("`{value}` takes no field `{name}`")));
}
}
match takes.iter().find(|name| record.get(name).is_none()) {
Some(missing) => Err(Located {
position: record.position,
problem: Problem::MissingField(missing),
}),
None => Ok(()),
}
}