use crate::executor::{CRITERION_TYPES, EXECUTORS};
use crate::fields::{CRITERION_FIELDS, STEP_FIELDS, TOP_FIELDS};
use crate::paths::PLACEHOLDER_NAMES;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Position {
Top,
Step(usize),
Criterion { step: usize, criterion: usize },
}
impl Position {
pub fn phrase(&self) -> String {
match self {
Position::Top => String::new(),
Position::Step(step) => format!("第 {step} 个步骤"),
Position::Criterion { step, criterion } => {
format!("第 {step} 个步骤第 {criterion} 条判据")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Fault {
TopNotMapping,
MissingName,
MissingSteps,
UnknownTopFields(Vec<String>),
MissingStepName,
UnknownStepFields(Vec<String>),
BadStepExecutor { got: String },
CriteriaNotList,
CriterionNotMapping,
BadCriterionExecutor,
UnknownCriterionFields(Vec<String>),
RuleNeedsJudgement,
ContainsNeedsFile,
FileNeedsContains,
OnlyOneJudgement,
NeedsDescription { kind: String },
NoRuleFields { kind: String, given: Vec<String> },
UnknownPlaceholder(Vec<String>),
}
impl Fault {
pub fn text(&self) -> String {
match self {
Fault::TopNotMapping => "的顶层不是映射(name / steps)".to_string(),
Fault::MissingName => "少了 name".to_string(),
Fault::MissingSteps => "少了 steps(至少一个步骤)".to_string(),
Fault::UnknownTopFields(unknown) => format!(
"顶层有不认识的字段:{}(只认 {})",
unknown.join("、"),
TOP_FIELDS.join("、")
),
Fault::MissingStepName => "少了 name".to_string(),
Fault::UnknownStepFields(unknown) => format!(
"有不认识的字段:{}(只认 {})",
unknown.join("、"),
STEP_FIELDS.join("、")
),
Fault::BadStepExecutor { got } => {
format!("的 executor 只能是 {},实得 {got}", EXECUTORS.join(" 或 "))
}
Fault::CriteriaNotList => "的 criteria 应当是列表".to_string(),
Fault::CriterionNotMapping => "不是映射".to_string(),
Fault::BadCriterionExecutor => format!(
"的 executor 只能是 {}(谁判:规则引擎 / 智能体 / 人)",
CRITERION_TYPES.join(" / ")
),
Fault::UnknownCriterionFields(unknown) => format!(
"有不认识的字段:{}(只认 {})",
unknown.join("、"),
CRITERION_FIELDS.join("、")
),
Fault::RuleNeedsJudgement => {
"是 rule,得写一条判法(path / absent / file+contains / run)".to_string()
}
Fault::ContainsNeedsFile => "写了 contains,还得写 file".to_string(),
Fault::FileNeedsContains => "写了 file,还得写 contains".to_string(),
Fault::OnlyOneJudgement => {
"的判法只能一种:path / absent / file+contains / run".to_string()
}
Fault::NeedsDescription { kind } => {
format!("是 {kind},必须写 description(判准 / 要人拍板的事)")
}
Fault::NoRuleFields { kind, given } => {
format!("是 {kind},不该带 {}(那是 rule 的字段)", given.join("、"))
}
Fault::UnknownPlaceholder(unknown) => {
let wrap = |names: Vec<String>| {
names
.into_iter()
.map(|name| format!("{{{{{name}}}}}"))
.collect::<Vec<_>>()
.join(" / ")
};
format!(
"的路径里有不认识的占位:{}(只认 {})",
wrap(unknown.clone()),
wrap(PLACEHOLDER_NAMES.iter().map(|n| n.to_string()).collect())
)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DefinitionError {
pub position: Position,
pub fault: Fault,
}
impl DefinitionError {
pub fn new(position: Position, fault: Fault) -> Self {
DefinitionError { position, fault }
}
pub fn message(&self, file: &str) -> String {
format!("{file} {}{}", self.position.phrase(), self.fault.text())
}
}
impl std::fmt::Display for DefinitionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.position.phrase(), self.fault.text())
}
}
impl std::error::Error for DefinitionError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn message_prefixes_the_file_and_keeps_the_position() {
let error = DefinitionError::new(Position::Top, Fault::MissingName);
assert_eq!(error.message("demo.yaml"), "demo.yaml 少了 name");
assert_eq!(error.to_string(), "少了 name");
let error = DefinitionError::new(
Position::Criterion {
step: 2,
criterion: 3,
},
Fault::RuleNeedsJudgement,
);
assert_eq!(
error.message("demo.yaml"),
"demo.yaml 第 2 个步骤第 3 条判据是 rule,得写一条判法(path / absent / file+contains / run)"
);
}
}