Skip to main content

torrust_tracker_deployer_lib/presentation/cli/controllers/show/
handler.rs

1//! Show Command Handler
2//!
3//! This module handles the show command execution at the presentation layer,
4//! displaying environment information with state-aware details.
5
6use std::cell::RefCell;
7use std::sync::Arc;
8
9use parking_lot::ReentrantMutex;
10
11use crate::application::command_handlers::show::info::EnvironmentInfo;
12use crate::application::command_handlers::show::{ShowCommandHandler, ShowCommandHandlerError};
13use crate::domain::environment::name::EnvironmentName;
14use crate::domain::environment::repository::EnvironmentRepository;
15use crate::presentation::cli::input::cli::OutputFormat;
16use crate::presentation::cli::views::commands::show::{JsonView, TextView};
17use crate::presentation::cli::views::progress::ProgressReporter;
18use crate::presentation::cli::views::Render;
19use crate::presentation::cli::views::UserOutput;
20
21use super::errors::ShowSubcommandError;
22
23/// Steps in the show workflow
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25enum ShowStep {
26    ValidateEnvironment,
27    LoadEnvironment,
28    DisplayInformation,
29}
30
31impl ShowStep {
32    /// All steps in execution order
33    const ALL: &'static [Self] = &[
34        Self::ValidateEnvironment,
35        Self::LoadEnvironment,
36        Self::DisplayInformation,
37    ];
38
39    /// Total number of steps
40    const fn count() -> usize {
41        Self::ALL.len()
42    }
43
44    /// User-facing description for the step
45    fn description(self) -> &'static str {
46        match self {
47            Self::ValidateEnvironment => "Validating environment name",
48            Self::LoadEnvironment => "Loading environment",
49            Self::DisplayInformation => "Displaying information",
50        }
51    }
52}
53
54/// Presentation layer controller for show command workflow
55///
56/// Displays environment information with state-aware details.
57/// This is a read-only command that shows stored data without remote verification.
58///
59/// ## Responsibilities
60///
61/// - Validate environment name format
62/// - Delegate to application layer for data extraction
63/// - Display state-aware information to the user
64/// - Provide next-step guidance based on current state
65///
66/// ## Architecture
67///
68/// This controller implements the Presentation Layer pattern, handling
69/// user interaction while delegating business logic to the application layer.
70pub struct ShowCommandController {
71    handler: ShowCommandHandler,
72    progress: ProgressReporter,
73}
74
75impl ShowCommandController {
76    /// Create a new `ShowCommandController` with dependencies
77    ///
78    /// # Arguments
79    ///
80    /// * `repository` - Environment repository for loading environment data
81    /// * `user_output` - Shared output service for user feedback
82    #[allow(clippy::needless_pass_by_value)] // Arc parameters are moved to constructor for ownership
83    pub fn new(
84        repository: Arc<dyn EnvironmentRepository + Send + Sync>,
85        user_output: Arc<ReentrantMutex<RefCell<UserOutput>>>,
86    ) -> Self {
87        let handler = ShowCommandHandler::new(repository);
88        let progress = ProgressReporter::new(user_output, ShowStep::count());
89
90        Self { handler, progress }
91    }
92
93    /// Execute the show command workflow
94    ///
95    /// This method orchestrates the three-step workflow:
96    /// 1. Validate environment name
97    /// 2. Load environment and extract info via application layer
98    /// 3. Display information to user
99    ///
100    /// # Arguments
101    ///
102    /// * `environment_name` - Name of the environment to show
103    /// * `output_format` - Output format (Text or Json)
104    ///
105    /// # Errors
106    ///
107    /// Returns `ShowSubcommandError` if any step fails
108    pub fn execute(
109        &mut self,
110        environment_name: &str,
111        output_format: OutputFormat,
112    ) -> Result<(), ShowSubcommandError> {
113        // Step 1: Validate environment name
114        let env_name = self.validate_environment_name(environment_name)?;
115
116        // Step 2: Load environment via application layer
117        let env_info = self.load_environment(&env_name)?;
118
119        // Step 3: Display information
120        self.display_information(&env_info, output_format)?;
121
122        Ok(())
123    }
124
125    /// Step 1: Validate environment name format
126    fn validate_environment_name(
127        &mut self,
128        name: &str,
129    ) -> Result<EnvironmentName, ShowSubcommandError> {
130        self.progress
131            .start_step(ShowStep::ValidateEnvironment.description())?;
132
133        let env_name = EnvironmentName::new(name.to_string()).map_err(|source| {
134            ShowSubcommandError::InvalidEnvironmentName {
135                name: name.to_string(),
136                source,
137            }
138        })?;
139
140        self.progress
141            .complete_step(Some(&format!("Environment name validated: {name}")))?;
142
143        Ok(env_name)
144    }
145
146    /// Step 2: Load environment via application layer
147    fn load_environment(
148        &mut self,
149        env_name: &EnvironmentName,
150    ) -> Result<EnvironmentInfo, ShowSubcommandError> {
151        self.progress
152            .start_step(ShowStep::LoadEnvironment.description())?;
153
154        let env_info = self
155            .handler
156            .execute(env_name)
157            .map_err(|e| Self::map_handler_error(e, env_name))?;
158
159        self.progress
160            .complete_step(Some(&format!("Environment loaded: {env_name}")))?;
161
162        Ok(env_info)
163    }
164
165    /// Map application layer errors to presentation errors
166    fn map_handler_error(
167        error: ShowCommandHandlerError,
168        env_name: &EnvironmentName,
169    ) -> ShowSubcommandError {
170        match error {
171            ShowCommandHandlerError::EnvironmentNotFound { .. } => {
172                ShowSubcommandError::EnvironmentNotFound {
173                    name: env_name.to_string(),
174                }
175            }
176            ShowCommandHandlerError::LoadError(e) => ShowSubcommandError::LoadError {
177                name: env_name.to_string(),
178                message: e.to_string(),
179            },
180        }
181    }
182
183    /// Step 3: Display environment information
184    ///
185    /// Orchestrates a functional pipeline to display environment information:
186    /// `EnvironmentInfo` → `String` → stdout
187    ///
188    /// The output is written to stdout (not stderr) as it represents the final
189    /// command result rather than progress information.
190    ///
191    /// # MVC Architecture
192    ///
193    /// Following the MVC pattern with functional composition:
194    /// - Model: `EnvironmentInfo` (application layer DTO)
195    /// - View: `TextView::render()` or `JsonView::render()` (formatting)
196    /// - Controller (this method): Orchestrates the pipeline
197    /// - Output: `ProgressReporter::result()` (routing to stdout)
198    fn display_information(
199        &mut self,
200        env_info: &EnvironmentInfo,
201        output_format: OutputFormat,
202    ) -> Result<(), ShowSubcommandError> {
203        self.progress
204            .start_step(ShowStep::DisplayInformation.description())?;
205
206        // Render using appropriate view based on output format (Strategy Pattern)
207        let output = match output_format {
208            OutputFormat::Text => TextView::render(env_info)?,
209            OutputFormat::Json => JsonView::render(env_info)?,
210        };
211
212        // Pipeline: EnvironmentInfo → render → output to stdout
213        self.progress.result(&output)?;
214
215        self.progress.complete_step(Some("Information displayed"))?;
216
217        Ok(())
218    }
219}