1use crate::app::effect::{AppEffect, AppEffectHandler, AppEffectResult, CommitResult};
8use std::path::{Path, PathBuf};
9
10pub struct RealAppEffectHandler {
11 workspace_root: Option<PathBuf>,
12}
13
14impl RealAppEffectHandler {
15 #[must_use]
16 pub const fn new() -> Self {
17 Self {
18 workspace_root: None,
19 }
20 }
21
22 #[must_use]
23 pub const fn with_workspace_root(root: PathBuf) -> Self {
24 Self {
25 workspace_root: Some(root),
26 }
27 }
28
29 fn resolve_path(&self, path: &Path) -> PathBuf {
30 crate::app::io::effect_io::resolve_path(&self.workspace_root, path)
31 }
32
33 fn execute_set_current_dir(&self, path: &Path) -> AppEffectResult {
34 let resolved = self.resolve_path(path);
35 match crate::app::io::effect_io::set_current_dir(&resolved) {
36 Ok(()) => AppEffectResult::Ok,
37 Err(error) => AppEffectResult::Error(format!(
38 "Failed to set current directory to '{}': {}",
39 resolved.display(),
40 error
41 )),
42 }
43 }
44
45 fn execute_write_file(&self, path: &Path, content: String) -> AppEffectResult {
46 let resolved = self.resolve_path(path);
47 match crate::app::io::effect_io::write_file(&resolved, content) {
48 Ok(()) => AppEffectResult::Ok,
49 Err(error) => AppEffectResult::Error(format!(
50 "Failed to write file '{}': {}",
51 resolved.display(),
52 error
53 )),
54 }
55 }
56
57 fn execute_read_file(&self, path: &Path) -> AppEffectResult {
58 let resolved = self.resolve_path(path);
59 match crate::app::io::effect_io::read_file(&resolved) {
60 Ok(content) => AppEffectResult::String(content),
61 Err(error) => AppEffectResult::Error(format!(
62 "Failed to read file '{}': {}",
63 resolved.display(),
64 error
65 )),
66 }
67 }
68
69 fn execute_delete_file(&self, path: &Path) -> AppEffectResult {
70 let resolved = self.resolve_path(path);
71 match crate::app::io::effect_io::delete_file(&resolved) {
72 Ok(()) => AppEffectResult::Ok,
73 Err(error) => AppEffectResult::Error(format!(
74 "Failed to delete file '{}': {}",
75 resolved.display(),
76 error
77 )),
78 }
79 }
80
81 fn execute_create_dir(&self, path: &Path) -> AppEffectResult {
82 let resolved = self.resolve_path(path);
83 match crate::app::io::effect_io::create_dir(&resolved) {
84 Ok(()) => AppEffectResult::Ok,
85 Err(error) => AppEffectResult::Error(format!(
86 "Failed to create directory '{}': {}",
87 resolved.display(),
88 error
89 )),
90 }
91 }
92
93 fn execute_path_exists(&self, path: &Path) -> AppEffectResult {
94 let resolved = self.resolve_path(path);
95 AppEffectResult::Bool(crate::app::io::effect_io::path_exists(&resolved))
96 }
97
98 fn execute_set_read_only(&self, path: &Path, readonly: bool) -> AppEffectResult {
99 let resolved = self.resolve_path(path);
100 match crate::app::io::effect_io::set_read_only(&resolved, readonly) {
101 Ok(()) => AppEffectResult::Ok,
102 Err(error) => AppEffectResult::Error(format!(
103 "Failed to set permissions on '{}': {}",
104 resolved.display(),
105 error
106 )),
107 }
108 }
109
110 fn execute_git_require_repo() -> AppEffectResult {
111 match crate::git_helpers::require_git_repo() {
112 Ok(()) => AppEffectResult::Ok,
113 Err(error) => AppEffectResult::Error(format!("Not in a git repository: {error}")),
114 }
115 }
116
117 fn execute_git_get_repo_root() -> AppEffectResult {
118 match crate::git_helpers::get_repo_root() {
119 Ok(root) => AppEffectResult::Path(root),
120 Err(error) => AppEffectResult::Error(format!("Failed to get repository root: {error}")),
121 }
122 }
123
124 fn execute_git_get_head_oid() -> AppEffectResult {
125 match crate::git_helpers::get_current_head_oid() {
126 Ok(oid) => AppEffectResult::String(oid),
127 Err(error) => AppEffectResult::Error(format!("Failed to get HEAD OID: {error}")),
128 }
129 }
130
131 fn execute_git_diff() -> AppEffectResult {
132 match crate::git_helpers::git_diff() {
133 Ok(diff) => AppEffectResult::String(diff),
134 Err(error) => AppEffectResult::Error(format!("Failed to get git diff: {error}")),
135 }
136 }
137
138 fn execute_git_diff_from(start_oid: &str) -> AppEffectResult {
139 match crate::git_helpers::git_diff_from(start_oid) {
140 Ok(diff) => AppEffectResult::String(diff),
141 Err(error) => {
142 AppEffectResult::Error(format!("Failed to get diff from '{start_oid}': {error}"))
143 }
144 }
145 }
146
147 fn execute_git_diff_from_start() -> AppEffectResult {
148 match crate::git_helpers::get_git_diff_from_start() {
149 Ok(diff) => AppEffectResult::String(diff),
150 Err(error) => {
151 AppEffectResult::Error(format!("Failed to get diff from start commit: {error}"))
152 }
153 }
154 }
155
156 fn execute_git_snapshot() -> AppEffectResult {
157 match crate::git_helpers::git_snapshot() {
158 Ok(snapshot) => AppEffectResult::String(snapshot),
159 Err(error) => AppEffectResult::Error(format!("Failed to create git snapshot: {error}")),
160 }
161 }
162
163 fn execute_git_add_all() -> AppEffectResult {
164 match crate::git_helpers::git_add_all() {
165 Ok(staged) => AppEffectResult::Bool(staged),
166 Err(error) => AppEffectResult::Error(format!("Failed to stage all changes: {error}")),
167 }
168 }
169
170 fn execute_git_commit(
171 message: &str,
172 user_name: Option<&str>,
173 user_email: Option<&str>,
174 ) -> AppEffectResult {
175 match crate::git_helpers::git_commit(message, user_name, user_email, None, None) {
176 Ok(Some(oid)) => AppEffectResult::Commit(CommitResult::Success(oid.to_string())),
177 Ok(None) => AppEffectResult::Commit(CommitResult::NoChanges),
178 Err(error) => AppEffectResult::Error(format!("Failed to create commit: {error}")),
179 }
180 }
181
182 fn execute_git_save_start_commit() -> AppEffectResult {
183 match crate::git_helpers::save_start_commit() {
184 Ok(()) => AppEffectResult::Ok,
185 Err(error) => AppEffectResult::Error(format!("Failed to save start commit: {error}")),
186 }
187 }
188
189 fn execute_git_reset_start_commit() -> AppEffectResult {
190 match crate::git_helpers::reset_start_commit() {
191 Ok(result) => AppEffectResult::String(result.oid),
192 Err(error) => AppEffectResult::Error(format!("Failed to reset start commit: {error}")),
193 }
194 }
195
196 fn execute_git_rebase_onto(_upstream_branch: String) -> AppEffectResult {
197 AppEffectResult::Error(
198 "GitRebaseOnto requires executor injection - use pipeline runner".to_string(),
199 )
200 }
201
202 fn execute_git_get_conflicted_files() -> AppEffectResult {
203 match crate::git_helpers::get_conflicted_files() {
204 Ok(files) => AppEffectResult::StringList(files),
205 Err(error) => {
206 AppEffectResult::Error(format!("Failed to get conflicted files: {error}"))
207 }
208 }
209 }
210
211 fn execute_git_continue_rebase() -> AppEffectResult {
212 AppEffectResult::Error(
213 "GitContinueRebase requires executor injection - use pipeline runner".to_string(),
214 )
215 }
216
217 fn execute_git_abort_rebase() -> AppEffectResult {
218 AppEffectResult::Error(
219 "GitAbortRebase requires executor injection - use pipeline runner".to_string(),
220 )
221 }
222
223 fn execute_git_get_default_branch() -> AppEffectResult {
224 match crate::git_helpers::get_default_branch() {
225 Ok(branch) => AppEffectResult::String(branch),
226 Err(error) => AppEffectResult::Error(format!("Failed to get default branch: {error}")),
227 }
228 }
229
230 fn execute_git_is_main_branch() -> AppEffectResult {
231 match crate::git_helpers::is_main_or_master_branch() {
232 Ok(is_main) => AppEffectResult::Bool(is_main),
233 Err(error) => AppEffectResult::Error(format!("Failed to check branch: {error}")),
234 }
235 }
236
237 fn execute_get_env_var(name: &str) -> AppEffectResult {
238 match crate::app::io::effect_io::get_env_var(name) {
239 Ok(value) => AppEffectResult::String(value),
240 Err(std::env::VarError::NotPresent) => {
241 AppEffectResult::Error(format!("Environment variable '{name}' not set"))
242 }
243 Err(std::env::VarError::NotUnicode(_)) => AppEffectResult::Error(format!(
244 "Environment variable '{name}' contains invalid Unicode"
245 )),
246 }
247 }
248
249 fn execute_set_env_var(name: &str, value: &str) -> AppEffectResult {
250 crate::app::io::effect_io::set_env_var(name, value);
251 AppEffectResult::Ok
252 }
253}
254
255impl Default for RealAppEffectHandler {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261impl AppEffectHandler for RealAppEffectHandler {
262 fn execute(&mut self, effect: AppEffect) -> AppEffectResult {
263 match effect {
264 AppEffect::SetCurrentDir { path } => self.execute_set_current_dir(&path),
265 AppEffect::WriteFile { path, content } => self.execute_write_file(&path, content),
266 AppEffect::ReadFile { path } => self.execute_read_file(&path),
267 AppEffect::DeleteFile { path } => self.execute_delete_file(&path),
268 AppEffect::CreateDir { path } => self.execute_create_dir(&path),
269 AppEffect::PathExists { path } => self.execute_path_exists(&path),
270 AppEffect::SetReadOnly { path, readonly } => {
271 self.execute_set_read_only(&path, readonly)
272 }
273 AppEffect::GitRequireRepo => Self::execute_git_require_repo(),
274 AppEffect::GitGetRepoRoot => Self::execute_git_get_repo_root(),
275 AppEffect::GitGetHeadOid => Self::execute_git_get_head_oid(),
276 AppEffect::GitDiff => Self::execute_git_diff(),
277 AppEffect::GitDiffFrom { start_oid } => Self::execute_git_diff_from(&start_oid),
278 AppEffect::GitDiffFromStart => Self::execute_git_diff_from_start(),
279 AppEffect::GitSnapshot => Self::execute_git_snapshot(),
280 AppEffect::GitAddAll => Self::execute_git_add_all(),
281 AppEffect::GitCommit {
282 message,
283 user_name,
284 user_email,
285 } => Self::execute_git_commit(&message, user_name.as_deref(), user_email.as_deref()),
286 AppEffect::GitSaveStartCommit => Self::execute_git_save_start_commit(),
287 AppEffect::GitResetStartCommit => Self::execute_git_reset_start_commit(),
288 AppEffect::GitRebaseOnto { upstream_branch } => {
289 Self::execute_git_rebase_onto(upstream_branch)
290 }
291 AppEffect::GitGetConflictedFiles => Self::execute_git_get_conflicted_files(),
292 AppEffect::GitContinueRebase => Self::execute_git_continue_rebase(),
293 AppEffect::GitAbortRebase => Self::execute_git_abort_rebase(),
294 AppEffect::GitGetDefaultBranch => Self::execute_git_get_default_branch(),
295 AppEffect::GitIsMainBranch => Self::execute_git_is_main_branch(),
296 AppEffect::GetEnvVar { name } => Self::execute_get_env_var(&name),
297 AppEffect::SetEnvVar { name, value } => Self::execute_set_env_var(&name, &value),
298 AppEffect::LogInfo { message: _ }
299 | AppEffect::LogSuccess { message: _ }
300 | AppEffect::LogWarn { message: _ }
301 | AppEffect::LogError { message: _ } => AppEffectResult::Ok,
302 }
303 }
304}