1use diffy::{Patch, apply as diffy_apply, create_patch as diffy_create_patch};
2
3#[cfg(feature = "wasm")]
4use wasm_bindgen::prelude::*;
5
6#[cfg_attr(feature = "wasm", wasm_bindgen)]
10pub fn apply_patch(original: &str, patch_text: &str) -> Result<String, String> {
11 let patch = Patch::from_str(patch_text).map_err(|err| format!("Invalid patch text: {err}"))?;
12 diffy_apply(original, &patch).map_err(|err| format!("Patch application failed: {err}"))
13}
14
15#[cfg_attr(feature = "wasm", wasm_bindgen)]
18pub fn create_patch(original: &str, modified: &str) -> String {
19 diffy_create_patch(original, modified).to_string()
20}
21
22#[cfg_attr(feature = "wasm", wasm_bindgen)]
30pub fn reconstruct_version(base: &str, patches_json: &str, target: u32) -> Result<String, String> {
31 if target == 0 {
32 return Ok(base.to_string());
33 }
34
35 let patches: Vec<String> =
36 serde_json::from_str(patches_json).map_err(|e| format!("Invalid patches JSON: {e}"))?;
37
38 let limit = (target as usize).min(patches.len());
39 let mut content = base.to_string();
40
41 for (i, patch_text) in patches.iter().take(limit).enumerate() {
42 content = apply_patch(&content, patch_text)
43 .map_err(|e| format!("Patch {} failed: {e}", i + 1))?;
44 }
45
46 Ok(content)
47}
48
49pub fn reconstruct_version_native(
53 base: &str,
54 patches: &[String],
55 target: usize,
56) -> Result<String, String> {
57 if target == 0 {
58 return Ok(base.to_string());
59 }
60 let limit = target.min(patches.len());
61 let mut content = base.to_string();
62 for (i, patch_text) in patches.iter().take(limit).enumerate() {
63 content = apply_patch(&content, patch_text)
64 .map_err(|e| format!("Patch {} failed: {e}", i + 1))?;
65 }
66 Ok(content)
67}
68
69pub fn squash_patches_native(base: &str, patches: &[String]) -> Result<String, String> {
71 let final_content = reconstruct_version_native(base, patches, patches.len())?;
72 Ok(create_patch(base, &final_content))
73}
74
75#[cfg_attr(feature = "wasm", wasm_bindgen)]
80pub fn squash_patches(base: &str, patches_json: &str) -> Result<String, String> {
81 let patches: Vec<String> =
82 serde_json::from_str(patches_json).map_err(|e| format!("Invalid patches JSON: {e}"))?;
83
84 let mut content = base.to_string();
85 for (i, patch_text) in patches.iter().enumerate() {
86 content = apply_patch(&content, patch_text)
87 .map_err(|e| format!("Patch {} failed: {e}", i + 1))?;
88 }
89
90 Ok(create_patch(base, &content))
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_create_and_apply_patch() {
99 let original = "Hello world. This is a test.\n";
100 let modified = "Hello beautiful world. This is an awesome test.\n";
101
102 let patch = create_patch(original, modified);
103 assert!(patch.contains("@@"));
104
105 let applied = apply_patch(original, &patch).expect("patch should apply");
106 assert_eq!(applied, modified);
107 }
108
109 #[test]
110 fn test_apply_invalid_patch() {
111 let result = apply_patch("Hello world\n", "@@ invalid patch format @@");
112 assert!(result.is_err());
113 }
114
115 #[test]
116 fn test_reconstruct_version_zero_returns_base() {
117 let base = "Hello world\n";
118 let result = reconstruct_version(base, "[]", 0).unwrap();
119 assert_eq!(result, base);
120 }
121
122 #[test]
123 fn test_reconstruct_version_applies_patches() {
124 let v0 = "line 1\n";
125 let v1 = "line 1\nline 2\n";
126 let v2 = "line 1\nline 2\nline 3\n";
127
128 let p1 = create_patch(v0, v1);
129 let p2 = create_patch(v1, v2);
130 let patches_json = serde_json::to_string(&vec![p1, p2]).unwrap();
131
132 let at_v1 = reconstruct_version(v0, &patches_json, 1).unwrap();
133 assert_eq!(at_v1, v1);
134
135 let at_v2 = reconstruct_version(v0, &patches_json, 2).unwrap();
136 assert_eq!(at_v2, v2);
137 }
138
139 #[test]
140 fn test_squash_patches_produces_single_diff() {
141 let v0 = "line 1\n";
142 let v1 = "line 1\nline 2\n";
143 let v2 = "line 1\nline 2\nline 3\n";
144
145 let p1 = create_patch(v0, v1);
146 let p2 = create_patch(v1, v2);
147 let patches_json = serde_json::to_string(&vec![p1, p2]).unwrap();
148
149 let squashed = squash_patches(v0, &patches_json).unwrap();
150 let result = apply_patch(v0, &squashed).unwrap();
151 assert_eq!(result, v2);
152 }
153
154 #[test]
155 fn test_reconstruct_version_native() {
156 let v0 = "line 1\n";
157 let v1 = "line 1\nline 2\n";
158 let v2 = "line 1\nline 2\nline 3\n";
159
160 let patches = vec![create_patch(v0, v1), create_patch(v1, v2)];
161 let at_v2 = reconstruct_version_native(v0, &patches, 2).unwrap();
162 assert_eq!(at_v2, v2);
163 }
164
165 #[test]
166 fn test_squash_patches_native() {
167 let v0 = "line 1\n";
168 let v1 = "line 1\nline 2\n";
169 let v2 = "line 1\nline 2\nline 3\n";
170
171 let patches = vec![create_patch(v0, v1), create_patch(v1, v2)];
172 let squashed = squash_patches_native(v0, &patches).unwrap();
173 let result = apply_patch(v0, &squashed).unwrap();
174 assert_eq!(result, v2);
175 }
176
177 #[test]
178 fn test_apply_conflicting_patch() {
179 let original = "Hello world. This is a test.\n";
180 let modified = "Hello beautiful world. This is an awesome test.\n";
181 let patch = create_patch(original, modified);
182
183 let result = apply_patch("Completely different text\n", &patch);
184 assert!(result.is_err());
185 }
186}