1use crate::core::git::GitOperations;
2use crate::{GitXError, Result};
3
4pub struct Validate;
6
7impl Validate {
8 pub fn commit_exists(commit: &str) -> Result<()> {
10 if GitOperations::commit_exists(commit)? {
11 Ok(())
12 } else {
13 Err(GitXError::GitCommand(format!(
14 "Commit '{commit}' does not exist"
15 )))
16 }
17 }
18
19 pub fn in_git_repo() -> Result<()> {
21 match GitOperations::repo_root() {
22 Ok(_) => Ok(()),
23 Err(_) => Err(GitXError::GitCommand("Not in a git repository".to_string())),
24 }
25 }
26
27 pub fn branch_name(name: &str) -> Result<()> {
29 if name.is_empty() {
30 return Err(GitXError::Parse("Branch name cannot be empty".to_string()));
31 }
32
33 let invalid_chars = [' ', '~', '^', ':', '?', '*', '[', '\\'];
35 if name.chars().any(|c| invalid_chars.contains(&c)) {
36 return Err(GitXError::Parse(format!(
37 "Branch name '{name}' contains invalid characters"
38 )));
39 }
40
41 if name == "HEAD" || name.starts_with('-') {
43 return Err(GitXError::Parse(format!(
44 "Branch name '{name}' is reserved"
45 )));
46 }
47
48 Ok(())
49 }
50
51 pub fn commit_hash(hash: &str) -> Result<()> {
53 if hash.is_empty() {
54 return Err(GitXError::Parse("Commit hash cannot be empty".to_string()));
55 }
56
57 if hash.len() < 4 || hash.len() > 40 {
59 return Err(GitXError::Parse(format!(
60 "Invalid commit hash format: '{hash}' (must be 4-40 characters)"
61 )));
62 }
63
64 if !hash.chars().all(|c| c.is_ascii_hexdigit()) {
66 return Err(GitXError::Parse(format!(
67 "Invalid commit hash format: '{hash}' (must contain only hexadecimal characters)"
68 )));
69 }
70
71 Ok(())
72 }
73
74 pub fn remote_name(name: &str) -> Result<()> {
76 if name.is_empty() {
77 return Err(GitXError::Parse("Remote name cannot be empty".to_string()));
78 }
79
80 let invalid_chars = [
82 ' ', '\t', '\n', '\r', '/', '\\', ':', '?', '*', '[', ']', '^', '~',
83 ];
84 if name.chars().any(|c| invalid_chars.contains(&c)) {
85 return Err(GitXError::Parse(format!(
86 "Remote name '{name}' contains invalid characters"
87 )));
88 }
89
90 if name.starts_with('-') || name.ends_with('-') || name.contains("..") {
92 return Err(GitXError::Parse(format!(
93 "Remote name '{name}' uses invalid pattern"
94 )));
95 }
96
97 Ok(())
98 }
99
100 pub fn file_path(path: &str) -> Result<()> {
102 if path.is_empty() {
103 return Err(GitXError::Parse("File path cannot be empty".to_string()));
104 }
105
106 if path.contains("..") || path.starts_with('/') {
108 return Err(GitXError::Parse(format!(
109 "File path '{path}' is not allowed (no .. or absolute paths)"
110 )));
111 }
112
113 if path.contains('\0') || path.contains('\r') || path.contains('\n') {
115 return Err(GitXError::Parse(format!(
116 "File path '{path}' contains invalid characters"
117 )));
118 }
119
120 Ok(())
121 }
122
123 pub fn positive_number(value: i32, max: Option<i32>, field_name: &str) -> Result<()> {
125 if value < 0 {
126 return Err(GitXError::Parse(format!(
127 "{field_name} must be positive, got {value}"
128 )));
129 }
130
131 if let Some(max_val) = max {
132 if value > max_val {
133 return Err(GitXError::Parse(format!(
134 "{field_name} must be <= {max_val}, got {value}"
135 )));
136 }
137 }
138
139 Ok(())
140 }
141
142 pub fn git_date_format(date_str: &str) -> Result<()> {
144 if date_str.is_empty() {
145 return Err(GitXError::Parse("Date string cannot be empty".to_string()));
146 }
147
148 if date_str.contains(';') || date_str.contains('&') || date_str.contains('|') {
150 return Err(GitXError::Parse(format!(
151 "Date string '{date_str}' contains invalid characters"
152 )));
153 }
154
155 if date_str.len() > 100 {
157 return Err(GitXError::Parse(format!(
158 "Date string too long: {}",
159 date_str.len()
160 )));
161 }
162
163 Ok(())
164 }
165
166 pub fn safe_string(input: &str, field_name: &str) -> Result<()> {
168 if input.is_empty() {
169 return Err(GitXError::Parse(format!("{field_name} cannot be empty")));
170 }
171
172 let dangerous_patterns = [
174 ";", "&", "|", "`", "$", "(", ")", "{", "}", "\\", "\n", "\r", " ",
175 ];
176 if dangerous_patterns
177 .iter()
178 .any(|&pattern| input.contains(pattern))
179 {
180 return Err(GitXError::Parse(format!(
181 "{field_name} contains potentially dangerous characters"
182 )));
183 }
184
185 if input.len() > 1000 {
187 return Err(GitXError::Parse(format!(
188 "{field_name} is too long: {} characters",
189 input.len()
190 )));
191 }
192
193 Ok(())
194 }
195}
196
197pub struct BranchNameValidator;
199
200impl crate::core::traits::Validator<str> for BranchNameValidator {
201 fn validate(&self, input: &str) -> Result<()> {
202 Validate::branch_name(input)
203 }
204
205 fn validation_rules(&self) -> Vec<&'static str> {
206 vec![
207 "Cannot be empty",
208 "Cannot start with a dash",
209 "Cannot be 'HEAD'",
210 "Cannot contain spaces",
211 "Cannot contain ~^:?*[\\",
212 ]
213 }
214}
215
216pub struct CommitHashValidator;
217
218impl crate::core::traits::Validator<str> for CommitHashValidator {
219 fn validate(&self, input: &str) -> Result<()> {
220 Validate::commit_hash(input)
221 }
222
223 fn validation_rules(&self) -> Vec<&'static str> {
224 vec![
225 "Must be 4-40 characters long",
226 "Must contain only hex characters (0-9, a-f)",
227 "Must reference an existing commit",
228 ]
229 }
230}
231
232pub struct RemoteNameValidator;
233
234impl crate::core::traits::Validator<str> for RemoteNameValidator {
235 fn validate(&self, input: &str) -> Result<()> {
236 Validate::remote_name(input)
237 }
238
239 fn validation_rules(&self) -> Vec<&'static str> {
240 vec![
241 "Cannot be empty",
242 "Cannot contain special characters",
243 "Cannot start or end with dash",
244 "Cannot contain '..'",
245 ]
246 }
247}