swift_mt_message/
scenario_config.rs1use serde_json::Value;
6use std::env;
7use std::fs;
8use std::path::{Path, PathBuf};
9
10use crate::errors::{ParseError, Result};
11
12#[derive(Debug, Clone)]
14pub struct ScenarioConfig {
15 pub base_paths: Vec<PathBuf>,
17}
18
19impl Default for ScenarioConfig {
20 fn default() -> Self {
21 if let Ok(env_paths) = env::var("SWIFT_SCENARIO_PATH") {
23 let paths = parse_env_paths(&env_paths);
24 if !paths.is_empty() {
25 return Self { base_paths: paths };
26 }
27 }
28
29 Self {
31 base_paths: vec![
32 PathBuf::from("test_scenarios"),
33 PathBuf::from("../test_scenarios"),
34 ],
35 }
36 }
37}
38
39impl ScenarioConfig {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn with_paths(paths: Vec<PathBuf>) -> Self {
47 Self { base_paths: paths }
48 }
49
50 pub fn add_path(mut self, path: PathBuf) -> Self {
52 self.base_paths.push(path);
53 self
54 }
55
56 pub fn set_paths(mut self, paths: Vec<PathBuf>) -> Self {
58 self.base_paths = paths;
59 self
60 }
61}
62
63fn parse_env_paths(env_value: &str) -> Vec<PathBuf> {
65 #[cfg(windows)]
67 let separator = ';';
68 #[cfg(not(windows))]
69 let separator = ':';
70
71 env_value
72 .split(separator)
73 .filter(|s| !s.is_empty())
74 .map(PathBuf::from)
75 .collect()
76}
77
78pub fn load_scenario_json<P: AsRef<Path>>(path: P) -> Result<Value> {
80 let content = fs::read_to_string(path).map_err(|e| ParseError::InvalidFormat {
81 message: format!("Failed to read scenario file: {e}"),
82 })?;
83
84 serde_json::from_str(&content).map_err(|e| ParseError::InvalidFormat {
85 message: format!("Failed to parse scenario JSON: {e}"),
86 })
87}
88
89pub fn find_scenario_for_message_type_with_config(
96 message_type: &str,
97 config: &ScenarioConfig,
98) -> Result<Value> {
99 for base_path in &config.base_paths {
100 let mt_dir = base_path.join(message_type.to_lowercase());
101
102 if !mt_dir.exists() {
104 continue;
105 }
106
107 let standard_path = mt_dir.join("standard.json");
109 if standard_path.exists() {
110 return load_scenario_json(standard_path);
111 }
112
113 let default_path = mt_dir.join("default.json");
115 if default_path.exists() {
116 return load_scenario_json(default_path);
117 }
118
119 if let Ok(entries) = fs::read_dir(&mt_dir) {
121 for entry in entries.flatten() {
122 let path = entry.path();
123
124 if path.extension().and_then(|s| s.to_str()) == Some("json") {
125 return load_scenario_json(path);
126 }
127 }
128 }
129 }
130
131 let searched_paths: Vec<String> = config
132 .base_paths
133 .iter()
134 .map(|p| format!("{}/{}", p.display(), message_type.to_lowercase()))
135 .collect();
136
137 Err(ParseError::InvalidFormat {
138 message: format!(
139 "No test scenarios found for message type: {}. Searched in: {}",
140 message_type.to_lowercase(),
141 searched_paths.join(", ")
142 ),
143 })
144}
145
146pub fn find_scenario_for_message_type(message_type: &str) -> Result<Value> {
153 find_scenario_for_message_type_with_config(message_type, &ScenarioConfig::default())
154}
155
156pub fn find_scenario_by_name_with_config(
158 message_type: &str,
159 scenario_name: &str,
160 config: &ScenarioConfig,
161) -> Result<Value> {
162 for base_path in &config.base_paths {
163 let scenario_path = base_path
164 .join(message_type.to_lowercase())
165 .join(format!("{scenario_name}.json"));
166
167 if scenario_path.exists() {
168 return load_scenario_json(scenario_path);
169 }
170 }
171
172 let tried_paths: Vec<String> = config
173 .base_paths
174 .iter()
175 .map(|p| {
176 format!(
177 "{}/{}/{}.json",
178 p.display(),
179 message_type.to_lowercase(),
180 scenario_name
181 )
182 })
183 .collect();
184
185 Err(ParseError::InvalidFormat {
186 message: format!(
187 "Scenario '{}' not found for {}. Tried paths: {}",
188 scenario_name,
189 message_type,
190 tried_paths.join(", ")
191 ),
192 })
193}
194
195pub fn find_scenario_by_name(message_type: &str, scenario_name: &str) -> Result<Value> {
197 find_scenario_by_name_with_config(message_type, scenario_name, &ScenarioConfig::default())
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use std::fs;
204 use tempfile::TempDir;
205
206 #[test]
207 fn test_scenario_config_default() {
208 let config = ScenarioConfig::default();
209 assert_eq!(config.base_paths.len(), 2);
210 assert_eq!(config.base_paths[0], PathBuf::from("test_scenarios"));
211 assert_eq!(config.base_paths[1], PathBuf::from("../test_scenarios"));
212 }
213
214 #[test]
215 fn test_scenario_config_with_paths() {
216 let paths = vec![
217 PathBuf::from("/custom/path1"),
218 PathBuf::from("/custom/path2"),
219 ];
220 let config = ScenarioConfig::with_paths(paths.clone());
221 assert_eq!(config.base_paths, paths);
222 }
223
224 #[test]
225 fn test_scenario_config_add_path() {
226 let config = ScenarioConfig::new()
227 .add_path(PathBuf::from("/path1"))
228 .add_path(PathBuf::from("/path2"));
229 assert!(config.base_paths.contains(&PathBuf::from("/path1")));
230 assert!(config.base_paths.contains(&PathBuf::from("/path2")));
231 }
232
233 #[test]
234 fn test_scenario_config_set_paths() {
235 let config = ScenarioConfig::new()
236 .add_path(PathBuf::from("/old"))
237 .set_paths(vec![PathBuf::from("/new1"), PathBuf::from("/new2")]);
238 assert_eq!(config.base_paths.len(), 2);
239 assert!(!config.base_paths.contains(&PathBuf::from("/old")));
240 assert!(config.base_paths.contains(&PathBuf::from("/new1")));
241 assert!(config.base_paths.contains(&PathBuf::from("/new2")));
242 }
243
244 #[test]
245 fn test_parse_env_paths_unix() {
246 #[cfg(not(windows))]
247 {
248 let paths = parse_env_paths("/path1:/path2:/path3");
249 assert_eq!(paths.len(), 3);
250 assert_eq!(paths[0], PathBuf::from("/path1"));
251 assert_eq!(paths[1], PathBuf::from("/path2"));
252 assert_eq!(paths[2], PathBuf::from("/path3"));
253 }
254 }
255
256 #[test]
257 fn test_parse_env_paths_empty_segments() {
258 #[cfg(not(windows))]
259 {
260 let paths = parse_env_paths("/path1::/path2:");
261 assert_eq!(paths.len(), 2);
262 assert_eq!(paths[0], PathBuf::from("/path1"));
263 assert_eq!(paths[1], PathBuf::from("/path2"));
264 }
265 }
266
267 #[test]
268 fn test_find_scenario_with_custom_config() {
269 let temp_dir = TempDir::new().unwrap();
271 let mt103_dir = temp_dir.path().join("mt103");
272 fs::create_dir(&mt103_dir).unwrap();
273
274 let scenario_json = r#"{
276 "basic_header": {
277 "app_id": "F",
278 "service_id": "01",
279 "ltp": "BANKUS33XXXX",
280 "session_number": "0001",
281 "sequence_number": "000001"
282 }
283 }"#;
284
285 let scenario_path = mt103_dir.join("test_scenario.json");
286 fs::write(&scenario_path, scenario_json).unwrap();
287
288 let config = ScenarioConfig::with_paths(vec![temp_dir.path().to_path_buf()]);
290
291 let result = find_scenario_for_message_type_with_config("MT103", &config);
293 assert!(result.is_ok());
294
295 let result = find_scenario_by_name_with_config("MT103", "test_scenario", &config);
297 assert!(result.is_ok());
298 }
299
300 #[test]
301 fn test_scenario_not_found_error() {
302 let config = ScenarioConfig::with_paths(vec![PathBuf::from("/nonexistent/path")]);
303
304 let result = find_scenario_for_message_type_with_config("MT999", &config);
305 assert!(result.is_err());
306
307 if let Err(e) = result {
308 let error_msg = format!("{:?}", e);
309 assert!(error_msg.contains("No test scenarios found"));
310 assert!(error_msg.contains("mt999"));
311 }
312 }
313}