use super::model::Criterion;
use crate::error::{DefinitionError, Fault, Position};
use crate::executor::{AGENT, CRITERION_TYPES, HUMAN, RULE};
use crate::fields::{CRITERION_FIELDS, text_of, unknown_fields};
use crate::paths::{PLACEHOLDER_NAMES, placeholders_in};
use serde_yaml::Value as Yaml;
pub fn criterion_of(value: &Yaml) -> Criterion {
let description = text_of(value, "description");
let kind = text_of(value, "executor");
if kind == AGENT {
return Criterion::AgentJudgement { description };
}
if kind == HUMAN {
return Criterion::HumanGate { description };
}
let path = text_of(value, "path");
if !path.is_empty() {
return Criterion::PathExists { path, description };
}
let absent = text_of(value, "absent");
if !absent.is_empty() {
return Criterion::PathAbsent {
absent,
description,
};
}
let file = text_of(value, "file");
if !file.is_empty() {
return Criterion::FileContains {
file,
contains: text_of(value, "contains"),
description,
};
}
Criterion::CommandRun {
run: text_of(value, "run"),
description,
}
}
pub fn read_criterion(
value: &Yaml,
step: usize,
order: usize,
) -> Result<Criterion, DefinitionError> {
let at = || Position::Criterion {
step,
criterion: order,
};
let criterion_map = value
.as_mapping()
.ok_or_else(|| DefinitionError::new(at(), Fault::CriterionNotMapping))?;
let kind = text_of(value, "executor");
if !CRITERION_TYPES.contains(&kind.as_str()) {
return Err(DefinitionError::new(at(), Fault::BadCriterionExecutor));
}
let odd = unknown_fields(criterion_map, &CRITERION_FIELDS);
if !odd.is_empty() {
return Err(DefinitionError::new(
at(),
Fault::UnknownCriterionFields(odd),
));
}
let given: Vec<&str> = ["path", "absent", "file", "contains", "run"]
.into_iter()
.filter(|name| value.get(*name).is_some())
.collect();
if kind == RULE {
if given.is_empty() {
return Err(DefinitionError::new(at(), Fault::RuleNeedsJudgement));
}
if given.contains(&"contains") && !given.contains(&"file") {
return Err(DefinitionError::new(at(), Fault::ContainsNeedsFile));
}
if given.contains(&"file") && !given.contains(&"contains") {
return Err(DefinitionError::new(at(), Fault::FileNeedsContains));
}
let others: Vec<&str> = given
.iter()
.copied()
.filter(|name| *name != "file" && *name != "contains")
.collect();
if others.len() > 1 || (!others.is_empty() && given.contains(&"file")) {
return Err(DefinitionError::new(at(), Fault::OnlyOneJudgement));
}
} else {
if text_of(value, "description").is_empty() {
return Err(DefinitionError::new(at(), Fault::NeedsDescription { kind }));
}
if !given.is_empty() {
return Err(DefinitionError::new(
at(),
Fault::NoRuleFields {
kind,
given: given.iter().map(|name| name.to_string()).collect(),
},
));
}
}
let mut unknown: Vec<String> = Vec::new();
for field in ["path", "absent", "file"] {
for name in placeholders_in(&text_of(value, field)) {
if !PLACEHOLDER_NAMES.contains(&name.as_str()) && !unknown.contains(&name) {
unknown.push(name);
}
}
}
if !unknown.is_empty() {
return Err(DefinitionError::new(
at(),
Fault::UnknownPlaceholder(unknown),
));
}
Ok(criterion_of(value))
}