1use crate::executor::{CRITERION_TYPES, EXECUTORS};
7use crate::fields::{CRITERION_FIELDS, STEP_FIELDS, TOP_FIELDS};
8use crate::paths::PLACEHOLDER_NAMES;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Position {
13 Top,
15 Step(usize),
17 Criterion { step: usize, criterion: usize },
19}
20
21impl Position {
22 pub fn phrase(&self) -> String {
24 match self {
25 Position::Top => String::new(),
26 Position::Step(step) => format!("第 {step} 个步骤"),
27 Position::Criterion { step, criterion } => {
28 format!("第 {step} 个步骤第 {criterion} 条判据")
29 }
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum Fault {
37 TopNotMapping,
39 MissingName,
41 MissingSteps,
43 UnknownTopFields(Vec<String>),
45 MissingStepName,
47 UnknownStepFields(Vec<String>),
49 BadStepExecutor { got: String },
51 CriteriaNotList,
53 CriterionNotMapping,
55 BadCriterionExecutor,
57 UnknownCriterionFields(Vec<String>),
59 RuleNeedsJudgement,
61 ContainsNeedsFile,
63 FileNeedsContains,
65 OnlyOneJudgement,
67 NeedsDescription { kind: String },
69 NoRuleFields { kind: String, given: Vec<String> },
71 UnknownPlaceholder(Vec<String>),
73}
74
75impl Fault {
76 pub fn text(&self) -> String {
78 match self {
79 Fault::TopNotMapping => "的顶层不是映射(name / steps)".to_string(),
80 Fault::MissingName => "少了 name".to_string(),
81 Fault::MissingSteps => "少了 steps(至少一个步骤)".to_string(),
82 Fault::UnknownTopFields(unknown) => format!(
83 "顶层有不认识的字段:{}(只认 {})",
84 unknown.join("、"),
85 TOP_FIELDS.join("、")
86 ),
87 Fault::MissingStepName => "少了 name".to_string(),
88 Fault::UnknownStepFields(unknown) => format!(
89 "有不认识的字段:{}(只认 {})",
90 unknown.join("、"),
91 STEP_FIELDS.join("、")
92 ),
93 Fault::BadStepExecutor { got } => {
94 format!("的 executor 只能是 {},实得 {got}", EXECUTORS.join(" 或 "))
95 }
96 Fault::CriteriaNotList => "的 criteria 应当是列表".to_string(),
97 Fault::CriterionNotMapping => "不是映射".to_string(),
98 Fault::BadCriterionExecutor => format!(
99 "的 executor 只能是 {}(谁判:规则引擎 / 智能体 / 人)",
100 CRITERION_TYPES.join(" / ")
101 ),
102 Fault::UnknownCriterionFields(unknown) => format!(
103 "有不认识的字段:{}(只认 {})",
104 unknown.join("、"),
105 CRITERION_FIELDS.join("、")
106 ),
107 Fault::RuleNeedsJudgement => {
108 "是 rule,得写一条判法(path / absent / file+contains / run)".to_string()
109 }
110 Fault::ContainsNeedsFile => "写了 contains,还得写 file".to_string(),
111 Fault::FileNeedsContains => "写了 file,还得写 contains".to_string(),
112 Fault::OnlyOneJudgement => {
113 "的判法只能一种:path / absent / file+contains / run".to_string()
114 }
115 Fault::NeedsDescription { kind } => {
116 format!("是 {kind},必须写 description(判准 / 要人拍板的事)")
117 }
118 Fault::NoRuleFields { kind, given } => {
119 format!("是 {kind},不该带 {}(那是 rule 的字段)", given.join("、"))
120 }
121 Fault::UnknownPlaceholder(unknown) => {
122 let wrap = |names: Vec<String>| {
123 names
124 .into_iter()
125 .map(|name| format!("{{{{{name}}}}}"))
126 .collect::<Vec<_>>()
127 .join(" / ")
128 };
129 format!(
130 "的路径里有不认识的占位:{}(只认 {})",
131 wrap(unknown.clone()),
132 wrap(PLACEHOLDER_NAMES.iter().map(|n| n.to_string()).collect())
133 )
134 }
135 }
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct DefinitionError {
142 pub position: Position,
144 pub fault: Fault,
146}
147
148impl DefinitionError {
149 pub fn new(position: Position, fault: Fault) -> Self {
150 DefinitionError { position, fault }
151 }
152
153 pub fn message(&self, file: &str) -> String {
155 format!("{file} {}{}", self.position.phrase(), self.fault.text())
156 }
157}
158
159impl std::fmt::Display for DefinitionError {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 write!(f, "{}{}", self.position.phrase(), self.fault.text())
162 }
163}
164
165impl std::error::Error for DefinitionError {}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn message_prefixes_the_file_and_keeps_the_position() {
173 let error = DefinitionError::new(Position::Top, Fault::MissingName);
174 assert_eq!(error.message("demo.yaml"), "demo.yaml 少了 name");
175 assert_eq!(error.to_string(), "少了 name");
176
177 let error = DefinitionError::new(
178 Position::Criterion {
179 step: 2,
180 criterion: 3,
181 },
182 Fault::RuleNeedsJudgement,
183 );
184 assert_eq!(
185 error.message("demo.yaml"),
186 "demo.yaml 第 2 个步骤第 3 条判据是 rule,得写一条判法(path / absent / file+contains / run)"
187 );
188 }
189}