torrust_tracker_deployer_lib/presentation/cli/controllers/docs/
handler.rs1use std::cell::RefCell;
19use std::path::PathBuf;
20use std::sync::Arc;
21
22use parking_lot::ReentrantMutex;
23
24use crate::infrastructure::cli_docs::CliDocsGenerator;
25use crate::presentation::cli::input::cli::Cli;
26use crate::presentation::cli::views::progress::ProgressReporter;
27use crate::presentation::cli::views::UserOutput;
28
29use super::errors::DocsCommandError;
30
31enum DocsStep {
33 GenerateDocs,
34}
35
36impl DocsStep {
37 fn description(&self) -> &str {
38 match self {
39 Self::GenerateDocs => "Generating CLI JSON documentation",
40 }
41 }
42
43 fn count() -> usize {
44 1
45 }
46}
47
48pub struct DocsCommandController {
53 progress: ProgressReporter,
54}
55
56impl DocsCommandController {
57 pub fn new(user_output: &Arc<ReentrantMutex<RefCell<UserOutput>>>) -> Self {
59 let progress = ProgressReporter::new(user_output.clone(), DocsStep::count());
60
61 Self { progress }
62 }
63
64 pub fn execute(&mut self, output_path: Option<&PathBuf>) -> Result<(), DocsCommandError> {
84 let docs = CliDocsGenerator::generate::<Cli>()
86 .map_err(|source| DocsCommandError::SchemaGenerationFailed { source })?;
87
88 if let Some(path) = output_path {
90 self.progress
92 .start_step(DocsStep::GenerateDocs.description())?;
93
94 if let Some(parent) = path.parent() {
96 std::fs::create_dir_all(parent).map_err(|source| {
97 DocsCommandError::DirectoryCreationFailed {
98 path: parent.to_path_buf(),
99 source,
100 }
101 })?;
102 }
103
104 std::fs::write(path, &docs).map_err(|source| DocsCommandError::FileWriteFailed {
106 path: path.clone(),
107 source,
108 })?;
109
110 self.progress
111 .complete_step(Some("CLI documentation written to file successfully"))?;
112 self.progress
113 .complete("CLI documentation generation completed successfully")?;
114 } else {
115 self.progress.result(&docs)?;
118 }
119
120 Ok(())
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::presentation::cli::views::testing::test_user_output::TestUserOutput;
128 use crate::presentation::cli::views::VerbosityLevel;
129 use tempfile::TempDir;
130
131 #[test]
132 fn it_should_generate_cli_schema_to_file_when_path_provided() {
133 let temp_dir = TempDir::new().unwrap();
134 let schema_path = temp_dir.path().join("docs.json");
135
136 let (user_output, _capture, _capture_stderr) =
137 TestUserOutput::new(VerbosityLevel::Normal).into_reentrant_wrapped();
138 let mut controller = DocsCommandController::new(&user_output);
139
140 let result = controller.execute(Some(&schema_path));
141 assert!(result.is_ok());
142
143 assert!(schema_path.exists());
145
146 let content = std::fs::read_to_string(&schema_path).unwrap();
148 assert!(content.contains("\"format\""));
149 assert!(content.contains("\"format_version\""));
150 assert!(content.contains("\"cli\""));
151 }
152
153 #[test]
154 fn it_should_complete_progress_when_generating_cli_schema() {
155 let (user_output, _capture, _capture_stderr) =
156 TestUserOutput::new(VerbosityLevel::Normal).into_reentrant_wrapped();
157 let mut controller = DocsCommandController::new(&user_output);
158
159 let temp_dir = TempDir::new().unwrap();
160 let schema_path = temp_dir.path().join("docs.json");
161
162 let result = controller.execute(Some(&schema_path));
163 assert!(result.is_ok());
164 }
165
166 #[test]
167 fn it_should_output_to_stdout_when_no_path_provided() {
168 let (user_output, capture, _capture_stderr) =
169 TestUserOutput::new(VerbosityLevel::Normal).into_reentrant_wrapped();
170 let mut controller = DocsCommandController::new(&user_output);
171
172 let result = controller.execute(None);
173 assert!(result.is_ok());
174
175 let output = String::from_utf8(capture.lock().clone()).unwrap();
177 assert!(output.contains("\"format\""));
178 assert!(output.contains("\"format_version\""));
179 assert!(output.contains("\"cli\""));
180 }
181
182 #[test]
183 fn it_should_generate_valid_cli_schema_structure() {
184 let (user_output, capture, _capture_stderr) =
185 TestUserOutput::new(VerbosityLevel::Normal).into_reentrant_wrapped();
186 let mut controller = DocsCommandController::new(&user_output);
187
188 let result = controller.execute(None);
189 assert!(result.is_ok());
190
191 let output = String::from_utf8(capture.lock().clone()).unwrap();
192 let json: serde_json::Value = serde_json::from_str(&output).unwrap();
193
194 assert!(json.get("format").is_some());
196 assert_eq!(json.get("format").unwrap(), "cli-documentation");
197 assert!(json.get("format_version").is_some());
198 assert!(json.get("cli").is_some());
199
200 let cli = json.get("cli").unwrap();
201 assert!(cli.get("name").is_some());
202 assert!(cli.get("version").is_some());
203 assert!(cli.get("commands").is_some());
204 }
205}