Skip to main content

sz_rust_workflow/definition/
parser.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-2026 SZ-Rust Team
3//
4use regex::Regex;
5
6use crate::error::{WorkflowError, WorkflowErrorCode, WorkflowResult};
7
8use super::models::{DefinitionFormat, FlowDefinition};
9
10/// 流程定义解析器,支持 YAML/JSON 格式。
11#[derive(Debug, Clone)]
12pub struct DefinitionParser {
13    flow_key_re: Regex,
14}
15
16impl DefinitionParser {
17    pub fn new() -> Self {
18        Self {
19            flow_key_re: Regex::new(r"^[a-z][a-z0-9_.]{0,63}$")
20                .expect("flow_key 正则编译失败(编译期常量,必然合法)"),
21        }
22    }
23
24    /// 解析流程定义文本。
25    ///
26    /// - `format`:显式指定格式;若传入 [`DefinitionFormat::Yaml`] 但文本首字符为 `{`,仍按 YAML 解析(serde_yaml 兼容 JSON 子集)
27    pub fn parse(&self, text: &str, format: DefinitionFormat) -> WorkflowResult<FlowDefinition> {
28        let def = match format {
29            DefinitionFormat::Json => serde_json::from_str::<FlowDefinition>(text)
30                .map_err(|e| Self::parse_error(text, &e.to_string()))?,
31            DefinitionFormat::Yaml => serde_yaml::from_str::<FlowDefinition>(text)
32                .map_err(|e| Self::parse_error(text, &e.to_string()))?,
33        };
34        self.validate_flow_key(&def)?;
35        Ok(def)
36    }
37
38    /// 自动检测格式并解析。
39    pub fn parse_auto(&self, text: &str) -> WorkflowResult<FlowDefinition> {
40        self.parse(text, DefinitionFormat::detect(text))
41    }
42
43    fn parse_error(text: &str, msg: &str) -> WorkflowError {
44        let snippet: String = text.chars().take(128).collect();
45        WorkflowError::new(
46            WorkflowErrorCode::FormatUnsupported,
47            format!("定义解析失败:{msg}"),
48        )
49        .with_details(serde_json::json!({ "snippet": snippet }))
50    }
51
52    fn validate_flow_key(&self, def: &FlowDefinition) -> WorkflowResult<()> {
53        if !self.flow_key_re.is_match(&def.flow_key) {
54            return Err(WorkflowError::with_field(
55                WorkflowErrorCode::FormatUnsupported,
56                "flow_key 命名违规:仅允许小写字母/数字/下划线/点号,长度 1~64,首字符须为小写字母",
57                "flow_key",
58                &def.flow_key,
59            ));
60        }
61        if def.name.is_empty() || def.name.chars().count() > 128 {
62            return Err(WorkflowError::with_field(
63                WorkflowErrorCode::FormatUnsupported,
64                "name 长度须为 1~128",
65                "name",
66                &def.name,
67            ));
68        }
69        Ok(())
70    }
71}
72
73impl Default for DefinitionParser {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    const VALID_YAML: &str = r#"
84flow_key: leave_request
85version: "1.0.0"
86name: 请假申请
87nodes:
88  - node_id: start
89    node_type: start
90    kind: start
91    next: end
92  - node_id: end
93    node_type: end
94    kind: end
95start_node: start
96active: true
97"#;
98
99    #[test]
100    fn parse_valid_yaml() {
101        let parser = DefinitionParser::new();
102        let def = parser.parse(VALID_YAML, DefinitionFormat::Yaml).unwrap();
103        assert_eq!(def.flow_key, "leave_request");
104        assert_eq!(def.name, "请假申请");
105        assert_eq!(def.nodes.len(), 2);
106    }
107
108    #[test]
109    fn parse_valid_json() {
110        let json = r#"{
111            "flow_key": "leave_req",
112            "version": "1.0.0",
113            "name": "请假",
114            "nodes": [
115                {"node_id": "start", "node_type": "start", "kind": "start", "next": "end"},
116                {"node_id": "end", "node_type": "end", "kind": "end"}
117            ],
118            "start_node": "start",
119            "active": true
120        }"#;
121        let parser = DefinitionParser::new();
122        let def = parser.parse(json, DefinitionFormat::Json).unwrap();
123        assert_eq!(def.flow_key, "leave_req");
124    }
125
126    #[test]
127    fn parse_auto_detect() {
128        let parser = DefinitionParser::new();
129        let def = parser.parse_auto(VALID_YAML).unwrap();
130        assert_eq!(def.flow_key, "leave_request");
131
132        let json = r#"{"flow_key":"x","version":"1.0.0","name":"x","nodes":[{"node_id":"s","node_type":"start","kind":"start","next":"e"},{"node_id":"e","node_type":"end","kind":"end"}],"start_node":"s"}"#;
133        let def2 = parser.parse_auto(json).unwrap();
134        assert_eq!(def2.flow_key, "x");
135    }
136
137    #[test]
138    fn parse_invalid_format() {
139        let parser = DefinitionParser::new();
140        let result = parser.parse("not a valid yaml: [", DefinitionFormat::Yaml);
141        assert!(result.is_err());
142        assert_eq!(
143            result.unwrap_err().code,
144            WorkflowErrorCode::FormatUnsupported
145        );
146    }
147
148    #[test]
149    fn parse_invalid_flow_key() {
150        let yaml = r#"
151flow_key: InvalidKey
152version: "1.0.0"
153name: test
154nodes:
155  - node_id: start
156    node_type: start
157    kind: start
158    next: end
159  - node_id: end
160    node_type: end
161    kind: end
162start_node: start
163"#;
164        let parser = DefinitionParser::new();
165        let result = parser.parse(yaml, DefinitionFormat::Yaml);
166        assert!(result.is_err());
167        assert_eq!(
168            result.unwrap_err().code,
169            WorkflowErrorCode::FormatUnsupported
170        );
171    }
172
173    #[test]
174    fn parse_empty_name() {
175        let yaml = r#"
176flow_key: valid_key
177version: "1.0.0"
178name: ""
179nodes:
180  - node_id: start
181    node_type: start
182    kind: start
183    next: end
184  - node_id: end
185    node_type: end
186    kind: end
187start_node: start
188"#;
189        let parser = DefinitionParser::new();
190        let result = parser.parse(yaml, DefinitionFormat::Yaml);
191        assert!(result.is_err());
192    }
193}