sz_rust_workflow/definition/
parser.rs1use regex::Regex;
5
6use crate::error::{WorkflowError, WorkflowErrorCode, WorkflowResult};
7
8use super::models::{DefinitionFormat, FlowDefinition};
9
10#[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 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 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}