better_config_core/utils/
misc.rs1use crate::error::Error;
3use std::path::Path;
4
5pub fn validate_and_split_paths(paths_str: &str) -> Result<Vec<String>, Error> {
19 if paths_str.trim().is_empty() {
20 return Err(Error::invalid_path(paths_str, "path cannot be empty"));
21 }
22
23 let file_paths: Vec<String> = paths_str
24 .split(',')
25 .map(|s| s.trim().to_string())
26 .filter(|s| !s.is_empty())
27 .collect();
28
29 if file_paths.is_empty() {
30 return Err(Error::invalid_path(paths_str, "no valid file paths found"));
31 }
32
33 for path in &file_paths {
35 if path.contains("..") {
36 return Err(Error::invalid_path(path, "path traversal not allowed"));
37 }
38 if path.len() > 260 {
39 return Err(Error::invalid_path(path, "path too long"));
40 }
41 if path
43 .chars()
44 .any(|c| matches!(c, '<' | '>' | ':' | '"' | '|' | '?' | '*'))
45 {
46 return Err(Error::invalid_path(path, "invalid characters in path"));
47 }
48 }
49
50 Ok(file_paths)
51}
52
53pub fn check_file_accessibility(path: &str) -> Result<(), Error> {
67 let path_obj = Path::new(path);
68 if !path_obj.exists() {
69 return Err(Error::file_not_found(path));
70 }
71 if !path_obj.is_file() {
72 return Err(Error::invalid_path(path, "path is not a file"));
73 }
74 Ok(())
75}
76
77pub fn safe_string_to_number<T: std::str::FromStr>(
93 s: &str,
94 key: &str,
95 type_name: &str,
96) -> Result<T, Error> {
97 s.parse()
98 .map_err(|_| Error::value_conversion_error(key, type_name, s))
99}
100
101#[cfg(test)]
102mod tests {
103 use crate::error::Error;
104 use crate::misc;
105
106 #[test]
107 fn test_validate_and_split_paths_valid() {
108 let result = misc::validate_and_split_paths("file1.json,file2.json");
109 assert!(result.is_ok());
110 assert_eq!(result.unwrap(), vec!["file1.json", "file2.json"]);
111 }
112
113 #[test]
114 fn test_validate_and_split_paths_empty() {
115 let result = misc::validate_and_split_paths("");
116 assert!(result.is_err());
117 match result.unwrap_err() {
118 Error::InvalidPathError { .. } => {}
119 _ => panic!("Expected InvalidPathError"),
120 }
121 }
122
123 #[test]
124 fn test_validate_and_split_paths_path_traversal() {
125 let result = misc::validate_and_split_paths("../config.json");
126 assert!(result.is_err());
127 match result.unwrap_err() {
128 Error::InvalidPathError { .. } => {}
129 _ => panic!("Expected InvalidPathError"),
130 }
131 }
132
133 #[test]
134 fn test_validate_and_split_paths_invalid_chars() {
135 let result = misc::validate_and_split_paths("config<test>.json");
136 assert!(result.is_err());
137 match result.unwrap_err() {
138 Error::InvalidPathError { .. } => {}
139 _ => panic!("Expected InvalidPathError"),
140 }
141 }
142
143 #[test]
144 fn test_check_file_accessibility_nonexistent() {
145 let result = misc::check_file_accessibility("nonexistent.json");
146 assert!(result.is_err());
147 match result.unwrap_err() {
148 Error::LoadFileError { .. } => {}
149 _ => panic!("Expected LoadFileError"),
150 }
151 }
152}