torrust_tracker_deployer_lib/presentation/cli/controllers/validate/
handler.rs1use std::cell::RefCell;
7use std::path::Path;
8use std::sync::Arc;
9
10use parking_lot::ReentrantMutex;
11
12use crate::application::command_handlers::validate::{ValidateCommandHandler, ValidationResult};
13use crate::presentation::cli::input::cli::OutputFormat;
14use crate::presentation::cli::views::commands::validate::{
15 JsonView, TextView, ValidateDetailsData,
16};
17use crate::presentation::cli::views::progress::ProgressReporter;
18use crate::presentation::cli::views::Render;
19use crate::presentation::cli::views::UserOutput;
20
21use super::errors::ValidateSubcommandError;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25enum ValidateStep {
26 LoadConfiguration,
27 ValidateSchema,
28 ValidateFields,
29}
30
31impl ValidateStep {
32 const ALL: &'static [Self] = &[
34 Self::LoadConfiguration,
35 Self::ValidateSchema,
36 Self::ValidateFields,
37 ];
38
39 const fn count() -> usize {
41 Self::ALL.len()
42 }
43
44 fn description(self) -> &'static str {
46 match self {
47 Self::LoadConfiguration => "Loading configuration file",
48 Self::ValidateSchema => "Validating JSON schema",
49 Self::ValidateFields => "Validating configuration fields",
50 }
51 }
52}
53
54pub struct ValidateCommandController {
72 progress: ProgressReporter,
73 handler: ValidateCommandHandler,
74}
75
76impl ValidateCommandController {
77 #[allow(clippy::needless_pass_by_value)] pub fn new(user_output: Arc<ReentrantMutex<RefCell<UserOutput>>>) -> Self {
83 Self {
84 progress: ProgressReporter::new(user_output, ValidateStep::count()),
85 handler: ValidateCommandHandler::new(),
86 }
87 }
88
89 pub fn execute(
110 &mut self,
111 env_file: &Path,
112 output_format: OutputFormat,
113 ) -> Result<(), ValidateSubcommandError> {
114 self.progress
116 .start_step(ValidateStep::LoadConfiguration.description())?;
117 Self::validate_file_exists(env_file)?;
118 self.progress
119 .complete_step(Some("Configuration file loaded"))?;
120
121 self.progress
123 .start_step(ValidateStep::ValidateSchema.description())?;
124
125 let result = self.handler.validate(env_file).map_err(|source| {
127 ValidateSubcommandError::ValidationFailed {
128 path: env_file.to_path_buf(),
129 source,
130 }
131 })?;
132
133 self.progress
134 .complete_step(Some("Schema validation passed"))?;
135
136 self.progress
138 .start_step(ValidateStep::ValidateFields.description())?;
139 self.progress
140 .complete_step(Some("Field validation passed"))?;
141
142 self.complete_workflow(env_file, &result, output_format)?;
144
145 Ok(())
146 }
147
148 fn validate_file_exists(env_file: &Path) -> Result<(), ValidateSubcommandError> {
150 if !env_file.exists() {
151 return Err(ValidateSubcommandError::ConfigFileNotFound {
152 path: env_file.to_path_buf(),
153 });
154 }
155
156 if !env_file.is_file() {
157 return Err(ValidateSubcommandError::ConfigPathNotFile {
158 path: env_file.to_path_buf(),
159 });
160 }
161
162 Ok(())
163 }
164
165 fn complete_workflow(
170 &mut self,
171 env_file: &Path,
172 result: &ValidationResult,
173 output_format: OutputFormat,
174 ) -> Result<(), ValidateSubcommandError> {
175 let data = ValidateDetailsData::from_result(env_file, result);
176
177 match output_format {
178 OutputFormat::Text => {
179 self.progress.blank_line()?;
180 self.progress.complete(&TextView::render(&data)?)?;
181 }
182 OutputFormat::Json => {
183 self.progress.result(&JsonView::render(&data)?)?;
184 }
185 }
186
187 Ok(())
188 }
189}