git_editor/utils/
validator.rs1use crate::args::Args;
2use crate::utils::types::Result;
3use colored::*;
4use regex::Regex;
5use std::process;
6use url::Url;
7
8pub fn validate_inputs(args: &Args) -> Result<()> {
9 let repo_path = args.repo_path.as_ref().unwrap();
11
12 if repo_path.is_empty() {
13 eprintln!("Repository path cannot be empty");
14 process::exit(1);
15 }
16 if Url::parse(repo_path).is_err() && !std::path::Path::new(repo_path).exists() {
17 eprintln!(
18 "{} {}",
19 "Invalid repository path or URL".red().bold(),
20 repo_path.yellow()
21 );
22 process::exit(1);
23 }
24 if std::path::Path::new(repo_path).exists() {
25 if !std::path::Path::new(repo_path).is_dir() {
26 eprintln!("Repository path is not a directory {repo_path}");
27 process::exit(1);
28 }
29 if !std::path::Path::new(repo_path).join(".git").exists() {
30 eprintln!("Repository path does not contain a valid Git repository {repo_path}");
31 process::exit(1);
32 }
33 }
34
35 if args.show_history || args.pic_specific_commits {
37 return Ok(());
38 }
39
40 let email = args.email.as_ref().unwrap();
42 let name = args.name.as_ref().unwrap();
43 let start = args.start.as_ref().unwrap();
44 let end = args.end.as_ref().unwrap();
45
46 let email_re = Regex::new(r"(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$")?;
47 if !email_re.is_match(email) {
48 eprintln!("{} {}", "Invalid email format".red().bold(), email.yellow());
49 process::exit(1);
50 }
51
52 if name.trim().is_empty() {
53 eprintln!("{}", "Name cannot be empty".red().bold());
54 process::exit(1);
55 }
56
57 let start_re = Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$")?;
58 if !start_re.is_match(start) {
59 eprintln!(
60 "{} {}",
61 "Invalid start date format".red().bold(),
62 start.yellow()
63 );
64 process::exit(1);
65 }
66
67 let end_re = Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$")?;
68 if !end_re.is_match(end) {
69 eprintln!(
70 "{} {}",
71 "Invalid end date format".red().bold(),
72 end.yellow()
73 );
74 process::exit(1);
75 }
76
77 if start >= end {
78 eprintln!("Start date must be before end date");
79 process::exit(1);
80 }
81
82 Ok(())
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use std::fs;
89 use tempfile::TempDir;
90
91 fn create_test_repo() -> (TempDir, String) {
92 let temp_dir = TempDir::new().unwrap();
93 let repo_path = temp_dir.path().to_str().unwrap().to_string();
94
95 let repo = git2::Repository::init(&repo_path).unwrap();
97
98 let file_path = temp_dir.path().join("test.txt");
100 fs::write(&file_path, "test content").unwrap();
101
102 let mut index = repo.index().unwrap();
104 index.add_path(std::path::Path::new("test.txt")).unwrap();
105 index.write().unwrap();
106
107 let tree_id = index.write_tree().unwrap();
108 let tree = repo.find_tree(tree_id).unwrap();
109
110 let sig = git2::Signature::new(
111 "Test User",
112 "test@example.com",
113 &git2::Time::new(1234567890, 0),
114 )
115 .unwrap();
116 repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])
117 .unwrap();
118
119 (temp_dir, repo_path)
120 }
121
122 #[test]
123 fn test_validate_inputs_show_history_mode() {
124 let (_temp_dir, repo_path) = create_test_repo();
125 let args = Args {
126 repo_path: Some(repo_path),
127 email: None,
128 name: None,
129 start: None,
130 end: None,
131 show_history: true,
132 pic_specific_commits: false,
133 };
134
135 let result = validate_inputs(&args);
136 assert!(result.is_ok());
137 }
138
139 #[test]
140 fn test_validate_inputs_pick_specific_commits_mode() {
141 let (_temp_dir, repo_path) = create_test_repo();
142 let args = Args {
143 repo_path: Some(repo_path),
144 email: None,
145 name: None,
146 start: None,
147 end: None,
148 show_history: false,
149 pic_specific_commits: true,
150 };
151
152 let result = validate_inputs(&args);
153 assert!(result.is_ok());
154 }
155
156 #[test]
157 fn test_validate_inputs_full_rewrite_valid() {
158 let (_temp_dir, repo_path) = create_test_repo();
159 let args = Args {
160 repo_path: Some(repo_path),
161 email: Some("test@example.com".to_string()),
162 name: Some("Test User".to_string()),
163 start: Some("2023-01-01 00:00:00".to_string()),
164 end: Some("2023-01-02 00:00:00".to_string()),
165 show_history: false,
166 pic_specific_commits: false,
167 };
168
169 let result = validate_inputs(&args);
170 assert!(result.is_ok());
171 }
172
173 #[test]
174 fn test_validate_inputs_invalid_email() {
175 let (_temp_dir, repo_path) = create_test_repo();
176 let _args = Args {
177 repo_path: Some(repo_path),
178 email: Some("invalid-email".to_string()),
179 name: Some("Test User".to_string()),
180 start: Some("2023-01-01 00:00:00".to_string()),
181 end: Some("2023-01-02 00:00:00".to_string()),
182 show_history: false,
183 pic_specific_commits: false,
184 };
185
186 let email_re = Regex::new(r"(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$").unwrap();
189 assert!(!email_re.is_match("invalid-email"));
190 assert!(email_re.is_match("test@example.com"));
191 }
192
193 #[test]
194 fn test_validate_inputs_invalid_date_format() {
195 let (_temp_dir, repo_path) = create_test_repo();
196 let _args = Args {
197 repo_path: Some(repo_path),
198 email: Some("test@example.com".to_string()),
199 name: Some("Test User".to_string()),
200 start: Some("invalid-date".to_string()),
201 end: Some("2023-01-02 00:00:00".to_string()),
202 show_history: false,
203 pic_specific_commits: false,
204 };
205
206 let start_re = Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$").unwrap();
207 assert!(!start_re.is_match("invalid-date"));
208 assert!(start_re.is_match("2023-01-01 00:00:00"));
209 }
210
211 #[test]
212 fn test_validate_inputs_nonexistent_repo() {
213 let _args = Args {
214 repo_path: Some("/nonexistent/path".to_string()),
215 email: Some("test@example.com".to_string()),
216 name: Some("Test User".to_string()),
217 start: Some("2023-01-01 00:00:00".to_string()),
218 end: Some("2023-01-02 00:00:00".to_string()),
219 show_history: false,
220 pic_specific_commits: false,
221 };
222
223 let repo_path = "/nonexistent/path";
225 assert!(!std::path::Path::new(repo_path).exists());
226 }
227
228 #[test]
229 fn test_email_regex_patterns() {
230 let email_re = Regex::new(r"(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$").unwrap();
231
232 assert!(email_re.is_match("test@example.com"));
234 assert!(email_re.is_match("user.name@domain.org"));
235 assert!(email_re.is_match("user+tag@example.co.uk"));
236 assert!(email_re.is_match("123@test.io"));
237
238 assert!(!email_re.is_match("invalid-email"));
240 assert!(!email_re.is_match("@domain.com"));
241 assert!(!email_re.is_match("user@"));
242 assert!(!email_re.is_match("user@domain"));
243 assert!(!email_re.is_match("user@domain."));
244 }
245
246 #[test]
247 fn test_datetime_regex_patterns() {
248 let datetime_re = Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$").unwrap();
249
250 assert!(datetime_re.is_match("2023-01-01 00:00:00"));
252 assert!(datetime_re.is_match("2023-12-31 23:59:59"));
253 assert!(datetime_re.is_match("2023-06-15 12:30:45"));
254
255 assert!(!datetime_re.is_match("2023-1-1 0:0:0"));
257 assert!(!datetime_re.is_match("2023/01/01 00:00:00"));
258 assert!(!datetime_re.is_match("2023-01-01T00:00:00"));
259 assert!(!datetime_re.is_match("23-01-01 00:00:00"));
260 assert!(!datetime_re.is_match("2023-01-01 00:00"));
261 }
262}