Skip to main content

quanttide_work/
error.rs

1//! 定义读不通时的错误。
2//!
3//! 只存位置(顶层 / 第 n 个步骤 / 第 n 个步骤第 m 条判据)与种类([`Fault`]);
4//! 文件名不进错误,由端侧在渲染时给——[`DefinitionError::message`] 出 canonical 文案。
5
6use crate::executor::{CRITERION_TYPES, EXECUTORS};
7use crate::fields::{CRITERION_FIELDS, STEP_FIELDS, TOP_FIELDS};
8use crate::paths::PLACEHOLDER_NAMES;
9
10/// 定义里的位置。
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Position {
13    /// 顶层。
14    Top,
15    /// 第 n 个步骤(从 1 数)。
16    Step(usize),
17    /// 第 n 个步骤第 m 条判据(都从 1 数)。
18    Criterion { step: usize, criterion: usize },
19}
20
21impl Position {
22    /// 位置的话头,比如「第 1 个步骤」;顶层是空串。
23    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/// 这一条错在哪,带上渲染所需的取值。
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum Fault {
37    /// 定义顶层不是映射。
38    TopNotMapping,
39    /// 顶层少了 name。
40    MissingName,
41    /// 顶层少了 steps(至少一个步骤)。
42    MissingSteps,
43    /// 顶层有不认识的字段。
44    UnknownTopFields(Vec<String>),
45    /// 步骤少了 name(步骤不是映射也算)。
46    MissingStepName,
47    /// 步骤有不认识的字段。
48    UnknownStepFields(Vec<String>),
49    /// 步骤的 executor 越界。
50    BadStepExecutor { got: String },
51    /// 步骤的 criteria 不是列表。
52    CriteriaNotList,
53    /// 判据不是映射。
54    CriterionNotMapping,
55    /// 判据的 executor 越界。
56    BadCriterionExecutor,
57    /// 判据有不认识的字段。
58    UnknownCriterionFields(Vec<String>),
59    /// 判据是 rule,却没写判法。
60    RuleNeedsJudgement,
61    /// 写了 contains,没写 file。
62    ContainsNeedsFile,
63    /// 写了 file,没写 contains。
64    FileNeedsContains,
65    /// 判法混着写:只能一种。
66    OnlyOneJudgement,
67    /// 判据是 agent / human,却没写 description。
68    NeedsDescription { kind: String },
69    /// 判据是 agent / human,却带了 rule 的字段。
70    NoRuleFields { kind: String, given: Vec<String> },
71    /// 判据的路径里写了不认识的占位。
72    UnknownPlaceholder(Vec<String>),
73}
74
75impl Fault {
76    /// 位置之后的那一句(canonical 文案的尾巴)。
77    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/// 一份定义读不通。
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct DefinitionError {
142    /// 在哪一层读不通。
143    pub position: Position,
144    /// 哪一条不成立。
145    pub fault: Fault,
146}
147
148impl DefinitionError {
149    pub fn new(position: Position, fault: Fault) -> Self {
150        DefinitionError { position, fault }
151    }
152
153    /// canonical 报错文字:`{file} {位置}{这一条}`;文件由端侧在渲染时给。
154    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}