sz_rust_workflow/
config.rs1use std::time::Duration;
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::{WorkflowError, WorkflowErrorCode, WorkflowResult};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct WorkflowConfig {
15 pub max_node_hops: u32,
17 pub plugin_call_timeout: Duration,
19 pub plugin_retry_max: u32,
21 pub plugin_retry_backoff: Duration,
23 pub guard_expr_max_length: usize,
25 pub context_max_size_kb: u32,
27 pub instance_recovery_batch: u32,
29}
30
31impl Default for WorkflowConfig {
32 fn default() -> Self {
33 Self {
34 max_node_hops: 10_000,
35 plugin_call_timeout: Duration::from_secs(5),
36 plugin_retry_max: 3,
37 plugin_retry_backoff: Duration::from_millis(100),
38 guard_expr_max_length: 1024,
39 context_max_size_kb: 256,
40 instance_recovery_batch: 500,
41 }
42 }
43}
44
45impl WorkflowConfig {
46 pub fn validate(&self) -> WorkflowResult<()> {
48 if self.max_node_hops < 100 || self.max_node_hops > 1_000_000 {
49 return Err(WorkflowError::with_field(
50 WorkflowErrorCode::StructureIncomplete,
51 "max_node_hops 越界,合法范围 [100, 1_000_000]",
52 "field",
53 "max_node_hops",
54 ));
55 }
56 if self.plugin_call_timeout < Duration::from_millis(100)
57 || self.plugin_call_timeout > Duration::from_secs(60)
58 {
59 return Err(WorkflowError::with_field(
60 WorkflowErrorCode::StructureIncomplete,
61 "plugin_call_timeout 越界,合法范围 [100ms, 60s]",
62 "field",
63 "plugin_call_timeout",
64 ));
65 }
66 if self.plugin_retry_max > 10 {
67 return Err(WorkflowError::with_field(
68 WorkflowErrorCode::StructureIncomplete,
69 "plugin_retry_max 越界,合法范围 [0, 10]",
70 "field",
71 "plugin_retry_max",
72 ));
73 }
74 if self.plugin_retry_backoff < Duration::from_millis(10)
75 || self.plugin_retry_backoff > Duration::from_secs(10)
76 {
77 return Err(WorkflowError::with_field(
78 WorkflowErrorCode::StructureIncomplete,
79 "plugin_retry_backoff 越界,合法范围 [10ms, 10s]",
80 "field",
81 "plugin_retry_backoff",
82 ));
83 }
84 if self.guard_expr_max_length < 64 || self.guard_expr_max_length > 8192 {
85 return Err(WorkflowError::with_field(
86 WorkflowErrorCode::StructureIncomplete,
87 "guard_expr_max_length 越界,合法范围 [64, 8192]",
88 "field",
89 "guard_expr_max_length",
90 ));
91 }
92 if self.context_max_size_kb < 16 || self.context_max_size_kb > 16384 {
93 return Err(WorkflowError::with_field(
94 WorkflowErrorCode::StructureIncomplete,
95 "context_max_size_kb 越界,合法范围 [16, 16384]",
96 "field",
97 "context_max_size_kb",
98 ));
99 }
100 if self.instance_recovery_batch < 10 || self.instance_recovery_batch > 10000 {
101 return Err(WorkflowError::with_field(
102 WorkflowErrorCode::StructureIncomplete,
103 "instance_recovery_batch 越界,合法范围 [10, 10000]",
104 "field",
105 "instance_recovery_batch",
106 ));
107 }
108 Ok(())
109 }
110}
111
112#[cfg(test)]
113#[allow(clippy::field_reassign_with_default)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn default_values() {
119 let cfg = WorkflowConfig::default();
120 assert_eq!(cfg.max_node_hops, 10_000);
121 assert_eq!(cfg.plugin_call_timeout, Duration::from_secs(5));
122 assert_eq!(cfg.plugin_retry_max, 3);
123 assert_eq!(cfg.plugin_retry_backoff, Duration::from_millis(100));
124 assert_eq!(cfg.guard_expr_max_length, 1024);
125 assert_eq!(cfg.context_max_size_kb, 256);
126 assert_eq!(cfg.instance_recovery_batch, 500);
127 }
128
129 #[test]
130 fn default_validates() {
131 assert!(WorkflowConfig::default().validate().is_ok());
132 }
133
134 #[test]
135 fn max_node_hops_out_of_range() {
136 let mut cfg = WorkflowConfig::default();
137 cfg.max_node_hops = 0;
138 assert!(cfg.validate().is_err());
139 cfg.max_node_hops = 99;
140 assert!(cfg.validate().is_err());
141 cfg.max_node_hops = 1_000_001;
142 assert!(cfg.validate().is_err());
143 cfg.max_node_hops = 100;
144 assert!(cfg.validate().is_ok());
145 cfg.max_node_hops = 1_000_000;
146 assert!(cfg.validate().is_ok());
147 }
148
149 #[test]
150 fn plugin_call_timeout_out_of_range() {
151 let mut cfg = WorkflowConfig::default();
152 cfg.plugin_call_timeout = Duration::from_millis(99);
153 assert!(cfg.validate().is_err());
154 cfg.plugin_call_timeout = Duration::from_secs(61);
155 assert!(cfg.validate().is_err());
156 cfg.plugin_call_timeout = Duration::from_millis(100);
157 assert!(cfg.validate().is_ok());
158 cfg.plugin_call_timeout = Duration::from_secs(60);
159 assert!(cfg.validate().is_ok());
160 }
161
162 #[test]
163 fn plugin_retry_max_out_of_range() {
164 let mut cfg = WorkflowConfig::default();
165 cfg.plugin_retry_max = 11;
166 assert!(cfg.validate().is_err());
167 cfg.plugin_retry_max = 10;
168 assert!(cfg.validate().is_ok());
169 cfg.plugin_retry_max = 0;
170 assert!(cfg.validate().is_ok());
171 }
172
173 #[test]
174 fn context_max_size_out_of_range() {
175 let mut cfg = WorkflowConfig::default();
176 cfg.context_max_size_kb = 0;
177 assert!(cfg.validate().is_err());
178 cfg.context_max_size_kb = 15;
179 assert!(cfg.validate().is_err());
180 cfg.context_max_size_kb = 16385;
181 assert!(cfg.validate().is_err());
182 cfg.context_max_size_kb = 16;
183 assert!(cfg.validate().is_ok());
184 cfg.context_max_size_kb = 16384;
185 assert!(cfg.validate().is_ok());
186 }
187
188 #[test]
189 fn yaml_deserialize() {
190 let yaml = r#"
191max_node_hops: 5000
192plugin_call_timeout:
193 secs: 10
194 nanos: 0
195plugin_retry_max: 5
196plugin_retry_backoff:
197 secs: 0
198 nanos: 200000000
199guard_expr_max_length: 2048
200context_max_size_kb: 512
201instance_recovery_batch: 1000
202"#;
203 let cfg: WorkflowConfig = serde_yaml::from_str(yaml).unwrap();
204 assert_eq!(cfg.max_node_hops, 5000);
205 assert_eq!(cfg.plugin_call_timeout, Duration::from_secs(10));
206 assert_eq!(cfg.plugin_retry_max, 5);
207 assert_eq!(cfg.plugin_retry_backoff, Duration::from_millis(200));
208 assert!(cfg.validate().is_ok());
209 }
210}