1use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FixupMode {
13 Preview,
15 Apply,
17}
18
19impl FixupMode {
20 #[must_use]
22 pub fn as_str(&self) -> &'static str {
23 match self {
24 Self::Preview => "preview",
25 Self::Apply => "apply",
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct DiffHunk {
35 pub start: usize,
37 pub remove_count: usize,
39 pub add_count: usize,
41 pub remove_lines: Vec<String>,
43 pub add_lines: Vec<String>,
45 pub old_range: (usize, usize),
47 pub new_range: (usize, usize),
49 pub content: String,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct UnifiedDiff {
58 pub path: String,
60 pub target_file: String,
62 pub diff_content: String,
64 pub hunks: Vec<DiffHunk>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct ChangeSummary {
73 pub hunk_count: usize,
75 pub lines_added: usize,
77 pub lines_removed: usize,
79 pub validation_passed: bool,
81 pub validation_messages: Vec<String>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct AppliedFile {
90 pub path: String,
92 pub blake3_first8: String,
94 pub applied: bool,
96 pub warnings: Vec<String>,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct FixupPreview {
105 pub target_files: Vec<String>,
107 pub change_summary: HashMap<String, ChangeSummary>,
109 pub warnings: Vec<String>,
111 pub all_valid: bool,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct FixupResult {
120 pub applied_files: Vec<AppliedFile>,
122 pub failed_files: Vec<String>,
124 pub warnings: Vec<String>,
126 pub three_way_used: bool,
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_fixup_mode() {
136 let preview = FixupMode::Preview;
137 let apply = FixupMode::Apply;
138
139 assert_eq!(preview, FixupMode::Preview);
140 assert_eq!(apply, FixupMode::Apply);
141 assert_ne!(preview, apply);
142 }
143
144 #[test]
145 fn test_diff_hunk() {
146 let hunk = DiffHunk {
147 start: 10,
148 remove_count: 2,
149 add_count: 3,
150 remove_lines: vec!["old line 1".to_string(), "old line 2".to_string()],
151 add_lines: vec![
152 "new line 1".to_string(),
153 "new line 2".to_string(),
154 "new line 3".to_string(),
155 ],
156 old_range: (10, 2),
157 new_range: (10, 3),
158 content:
159 "@@ -10,2 +10,3 @@\n-old line 1\n-old line 2\n+new line 1\n+new line 2\n+new line 3"
160 .to_string(),
161 };
162
163 assert_eq!(hunk.start, 10);
164 assert_eq!(hunk.remove_count, 2);
165 assert_eq!(hunk.add_count, 3);
166 assert_eq!(hunk.remove_lines.len(), 2);
167 assert_eq!(hunk.add_lines.len(), 3);
168 assert_eq!(hunk.old_range, (10, 2));
169 assert_eq!(hunk.new_range, (10, 3));
170 }
171
172 #[test]
173 fn test_unified_diff() {
174 let hunk = DiffHunk {
175 start: 1,
176 remove_count: 1,
177 add_count: 1,
178 remove_lines: vec!["old".to_string()],
179 add_lines: vec!["new".to_string()],
180 old_range: (1, 1),
181 new_range: (1, 1),
182 content: "@@ -1,1 +1,1 @@\n-old\n+new".to_string(),
183 };
184
185 let diff = UnifiedDiff {
186 path: "a/src/main.rs".to_string(),
187 target_file: "src/main.rs".to_string(),
188 diff_content: "--- a/src/main.rs\n+++ b/src/main.rs\n@@ -1,1 +1,1 @@\n-old\n+new"
189 .to_string(),
190 hunks: vec![hunk],
191 };
192
193 assert_eq!(diff.path, "a/src/main.rs");
194 assert_eq!(diff.target_file, "src/main.rs");
195 assert_eq!(diff.hunks.len(), 1);
196 }
197
198 #[test]
199 fn test_change_summary() {
200 let summary = ChangeSummary {
201 hunk_count: 2,
202 lines_added: 5,
203 lines_removed: 3,
204 validation_passed: true,
205 validation_messages: vec![],
206 };
207
208 assert_eq!(summary.hunk_count, 2);
209 assert_eq!(summary.lines_added, 5);
210 assert_eq!(summary.lines_removed, 3);
211 assert!(summary.validation_passed);
212 }
213
214 #[test]
215 fn test_applied_file() {
216 let applied = AppliedFile {
217 path: "src/main.rs".to_string(),
218 blake3_first8: "a1b2c3d4".to_string(),
219 applied: true,
220 warnings: vec![],
221 };
222
223 assert_eq!(applied.path, "src/main.rs");
224 assert_eq!(applied.blake3_first8, "a1b2c3d4");
225 assert!(applied.applied);
226 }
227
228 #[test]
229 fn test_fixup_preview() {
230 let mut summary = HashMap::new();
231 summary.insert(
232 "src/main.rs".to_string(),
233 ChangeSummary {
234 hunk_count: 1,
235 lines_added: 2,
236 lines_removed: 1,
237 validation_passed: true,
238 validation_messages: vec![],
239 },
240 );
241
242 let preview = FixupPreview {
243 target_files: vec!["src/main.rs".to_string()],
244 change_summary: summary,
245 warnings: vec![],
246 all_valid: true,
247 };
248
249 assert_eq!(preview.target_files.len(), 1);
250 assert!(preview.all_valid);
251 assert!(preview.change_summary.contains_key("src/main.rs"));
252 }
253
254 #[test]
255 fn test_fixup_result() {
256 let result = FixupResult {
257 applied_files: vec![AppliedFile {
258 path: "src/main.rs".to_string(),
259 blake3_first8: "a1b2c3d4".to_string(),
260 applied: true,
261 warnings: vec![],
262 }],
263 failed_files: vec![],
264 warnings: vec![],
265 three_way_used: false,
266 };
267
268 assert_eq!(result.applied_files.len(), 1);
269 assert_eq!(result.failed_files.len(), 0);
270 assert!(!result.three_way_used);
271 }
272}