- 
                Notifications
    
You must be signed in to change notification settings  - Fork 0
 
refactor: [#107] centralize UserOutput via dependency injection #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: [#107] centralize UserOutput via dependency injection #108
Conversation
- Add AppContainer for centralized dependency management - Update all command handlers to accept &Arc<Mutex<UserOutput>> - Remove UserOutput cloning throughout command execution - Update all documentation examples to use Arc<Mutex<UserOutput>> - Ensure thread-safe shared ownership of output stream This centralizes UserOutput creation and management, providing better control over output formatting and reducing duplication.
- Remove duplicate context creation in handle_environment_creation - Remove duplicate context creation in handle_destroy_command - Remove unused into_user_output method from CommandContext - Clone Arc<Mutex<UserOutput>> instead of consuming context This improves efficiency by reusing the context throughout command execution instead of recreating it unnecessarily.
- Remove report_error function from CommandContext - Call UserOutput::error() directly where needed - Simplifies error reporting by eliminating indirection - All error reporting now uses consistent pattern: user_output.lock().unwrap().error() This reduces unnecessary abstraction and makes the code more straightforward by calling the UserOutput service directly.
- Add create_test_setup() helper function in context.rs tests - Refactor 7 test functions to use the helper instead of repeated setup code - Reduces 21 lines of duplicated setup to 7 lines using helper - Fix clippy warnings: add backticks to doc comments and Panics section - Improves test maintainability by centralizing test setup logic
- Add create_test_setup() helper function in factory.rs tests - Refactor 6 test functions to use helper instead of repeated setup - Reduces 24 lines of duplicated setup code to 6 lines using helper - Improves test maintainability by centralizing factory test setup
- Created ProgressReporterError enum for progress reporting failures - Updated ProgressReporter methods to return Result<(), ProgressReporterError> - Added ProgressReportingFailed variant to DestroySubcommandError and CreateSubcommandError - Replaced all .expect() calls with .map_err() for proper error propagation - Removed problematic 'if let Ok(mut output)' patterns that silently ignored errors - Updated all 26 test cases to handle Result returns with .expect() - Added comprehensive help text for all new error variants - Updated documentation with # Errors sections and Result examples This eliminates all mutex poisoning panics, ensuring errors are properly propagated through the call stack with full traceability and actionable error messages for users.
…ReporterError
- Add From<ProgressReporterError> implementations for DestroySubcommandError and CreateSubcommandError
- Replace verbose .map_err(|e| Error::ProgressReportingFailed { source: e }) with simple ? operator
- Eliminates ~80 lines of boilerplate across destroy and create handlers
- Follows idiomatic Rust patterns for automatic error conversion
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the UserOutput service to use thread-safe shared ownership via Arc<Mutex<UserOutput>> instead of direct ownership, enabling consistent output across concurrent operations and multiple components. This architectural change addresses concurrency concerns and establishes a centralized service container pattern for dependency injection.
Key Changes:
- Introduced 
Arc<Mutex<UserOutput>>wrapper pattern for thread-safe shared access to user output - Added 
ProgressReporterErrorenum with proper error handling for mutex poisoning - Created 
Containerservice container in bootstrap layer for centralized dependency injection - Updated all command handlers and progress reporting to use the new shared output pattern
 
Reviewed Changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description | 
|---|---|
src/presentation/progress.rs | 
Refactored ProgressReporter to accept Arc<Mutex<UserOutput>>, added error handling for mutex operations, updated all methods to return Result | 
src/presentation/errors.rs | 
Added UserOutputLockFailed error variant to CommandError with comprehensive help text | 
src/presentation/commands/mod.rs | 
Updated execute and handle_error functions to accept shared UserOutput reference, added fallback error reporting | 
src/presentation/commands/factory.rs | 
Modified create_context to require user_output parameter, updated all factory methods and tests | 
src/presentation/commands/context.rs | 
Replaced owned UserOutput with Arc<Mutex<UserOutput>>, removed into_output() and report_error() functions, updated tests | 
src/presentation/commands/create/handler.rs | 
Updated to pass user_output through to subcommand handlers | 
src/presentation/commands/create/subcommands/environment.rs | 
Refactored to use shared UserOutput, updated error reporting with mutex lock attempts | 
src/presentation/commands/create/subcommands/template.rs | 
Updated to accept and lock user_output parameter | 
src/presentation/commands/create/errors.rs | 
Added UserOutputLockFailed and ProgressReportingFailed error variants with detailed help | 
src/presentation/commands/destroy/handler.rs | 
Updated to use shared UserOutput, removed inline error reporting | 
src/presentation/commands/destroy/errors.rs | 
Added mutex poisoning and progress reporting error variants with comprehensive help | 
src/presentation/commands/tests/mod.rs | 
Added user_output field to TestContext for testing | 
src/bootstrap/container.rs | 
New file: Implements service container for centralized dependency injection | 
src/bootstrap/app.rs | 
Integrated Container for initializing and providing shared services to commands | 
| )); | ||
| user_output | ||
| .lock() | ||
| .map_err(|_| crate::presentation::progress::ProgressReporterError::UserOutputMutexPoisoned)? | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error mapping is converting a mutex poisoning error to ProgressReporterError::UserOutputMutexPoisoned, but the function returns CreateSubcommandError. This works because there's a From implementation, but it's semantically incorrect - this is not a progress reporting error, it's occurring during configuration loading. Consider mapping to CreateSubcommandError::UserOutputLockFailed directly instead, or create a helper function to avoid the confusing intermediate conversion.
| .map_err(|_| crate::presentation::progress::ProgressReporterError::UserOutputMutexPoisoned)? | |
| .map_err(|_| CreateSubcommandError::UserOutputLockFailed)? | 
| output.progress("Validating configuration and creating environment..."); | ||
| user_output | ||
| .lock() | ||
| .map_err(|_| crate::presentation::progress::ProgressReporterError::UserOutputMutexPoisoned)? | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as Comment 1 - these mutex lock failures in execute_create_command are being mapped to ProgressReporterError::UserOutputMutexPoisoned when they should map to CreateSubcommandError::UserOutputLockFailed. The semantic mismatch makes error tracing and debugging more difficult. These are not progress reporting errors, they're user output access errors during command execution.
| 
               | 
          ||
| user_output | ||
| .lock() | ||
| .map_err(|_| crate::presentation::progress::ProgressReporterError::UserOutputMutexPoisoned)? | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as Comment 1 - these mutex lock failures in execute_create_command are being mapped to ProgressReporterError::UserOutputMutexPoisoned when they should map to CreateSubcommandError::UserOutputLockFailed. The semantic mismatch makes error tracing and debugging more difficult. These are not progress reporting errors, they're user output access errors during command execution.
| /// # Panics | ||
| /// | ||
| /// This function will panic if the `UserOutput` mutex is poisoned. Since this is | ||
| /// called after successful environment creation (when operation is complete), | ||
| /// a poisoned mutex indicates an irrecoverable state and panicking is acceptable. | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The panic documentation is technically accurate but philosophically inconsistent with the rest of the PR's error handling approach. Throughout the PR, mutex poisoning is carefully handled with proper error types and user-friendly messages. Here, it's allowed to panic with expect() at line 205. While the justification is reasonable (operation already succeeded), consider whether this should return a Result instead for consistency, or at least use a more informative panic message that matches the help text from the error variants.
| 
               | 
          ||
| // Create progress reporter for 3 main steps | ||
| let mut progress = ProgressReporter::new(ctx.into_output(), 3); | ||
| let mut progress = ProgressReporter::new(user_output.clone(), 3); | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user_output is cloned here and then immediately cloned again on line 68 for the ProgressReporter. Since Arc::clone() is cheap (just incrementing a reference count), this is not a major issue, but it's unnecessary - the second clone could be avoided by passing ctx.user_output() to ProgressReporter::new() instead of user_output.clone(). This would make the ownership flow clearer and eliminate one redundant clone.
| let mut progress = ProgressReporter::new(user_output.clone(), 3); | |
| let mut progress = ProgressReporter::new(ctx.user_output(), 3); | 
| 
               | 
          ||
| // Create progress reporter for 3 main steps | ||
| let mut progress = ProgressReporter::new(ctx.into_output(), 3); | ||
| let mut progress = ProgressReporter::new(user_output.clone(), 3); | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same performance issue as Comment 4 - user_output is cloned twice unnecessarily. The second clone for ProgressReporter::new() could use ctx.user_output() instead, eliminating a redundant Arc::clone() operation and making the ownership flow clearer.
| let mut progress = ProgressReporter::new(user_output.clone(), 3); | |
| let mut progress = ProgressReporter::new(ctx.user_output(), 3); | 
| if let Ok(mut output) = user_output.lock() { | ||
| output.error(&format!("{error}")); | ||
| output.blank_line(); | ||
| output.info_block("For detailed troubleshooting:", &[help_text]); | ||
| } else { | ||
| // Cannot acquire lock - print to stderr directly as fallback | ||
| eprintln!("ERROR: {error}"); | ||
| eprintln!(); | ||
| eprintln!("CRITICAL: Failed to acquire user output lock."); | ||
| eprintln!("This indicates a panic occurred in another thread."); | ||
| eprintln!(); | ||
| eprintln!("For detailed troubleshooting:"); | ||
| eprintln!("{help_text}"); | ||
| } | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The fallback error handling using eprintln! directly is a good defensive programming practice. However, the fallback messages lack the same formatting and visual indicators (emojis, styling) that the normal UserOutput provides. Consider whether the fallback should attempt to maintain consistent formatting, or if this plain text approach is intentional for the degraded error state. Document the rationale in a comment if the plain text is intentional.
- Fix error mapping: Use CreateSubcommandError::UserOutputLockFailed instead of ProgressReporterError::UserOutputMutexPoisoned - Eliminate unnecessary clones: Use ctx.user_output().clone() directly for ProgressReporter initialization - Enhance panic message with detailed context about UserOutput lock poisoning - Document fallback error formatting rationale in handle_error() All changes address PR #108 review comments from GitHub Copilot.
…ethods - Create DestroyCommandController struct to encapsulate workflow orchestration - Extract private step methods: validate_environment_name, initialize_dependencies, tear_down_infrastructure, complete_workflow - Add DESTROY_WORKFLOW_STEPS constant for step count - Simplify handle_destroy_command to be a thin wrapper over controller - Improve testability by separating concerns between presentation and application layers - Add comprehensive documentation with # Errors sections for public methods This refactor improves code clarity, maintainability, and testability while maintaining backward compatibility with existing tests.
| 
           ACK 75af108  | 
    
Summary
This PR centralizes
UserOutputmanagement via dependency injection, providing better control over output formatting and reducing duplication throughout the codebase.Changes
Core Changes
AppContainerfor centralized dependency management (src/bootstrap/container.rs)&Arc<Mutex<UserOutput>>UserOutputcloning throughout command executionDocumentation
Arc<Mutex<UserOutput>>Testing
Benefits
Files Changed (20 files)
src/bootstrap/app.rs- Updated to use AppContainersrc/bootstrap/container.rs- NEW - Dependency injection containersrc/bootstrap/mod.rs- Module updatessrc/presentation/commands/context.rs- Updated API and docssrc/presentation/commands/create/*- Updated create command handlerssrc/presentation/commands/destroy/*- Updated destroy command handlerssrc/presentation/commands/factory.rs- Updated factory methodssrc/presentation/commands/mod.rs- Updated command executionsrc/presentation/progress.rs- Updated progress reportingStatus
Related
Closes #107