Skip to content

Commit 36f9627

Browse files
committed
refactor: [#116] replace Box<dyn Error> with concrete error enums in dependency-installer
- Replace all Box<dyn std::error::Error> with strongly-typed enum errors - Implement From traits for automatic error conversions using ? operator - Create ExitCode enum for type-safe CLI exit codes - Move AppError::to_exit_code logic from binary to AppError method - Relocate DetectionError to detector module where it originates - Relocate CommandError to command module where it originates - Remove now-empty errors.rs module Error hierarchy established: - AppError (app.rs) - top-level application errors - CheckFailed wraps CheckError - ListFailed wraps ListError - CheckError (handlers/check.rs) - check command errors - CheckAllFailed wraps CheckAllToolsError - CheckSpecificFailed wraps CheckSpecificToolError - CheckAllToolsError - checking all dependencies - MissingDependencies with counts - DependencyCheckFailed wraps DetectionError - CheckSpecificToolError - checking specific tool - ParseFailed wraps ParseToolNameError - ToolNotInstalled with tool name - DetectionFailed wraps DetectionError - ParseToolNameError - tool name parsing - UnknownTool with name and available_tools - ListError (handlers/list.rs) - list command errors - DependencyCheckFailed wraps DetectionError - DetectionError (detector/mod.rs) - tool detection - CommandError (command.rs) - command execution - ExitCode enum (app.rs) - type-safe exit codes All errors use thiserror with #[error] and #[source] attributes for complete traceability. Pattern matching now possible on all error types.
1 parent 7e461ed commit 36f9627

File tree

13 files changed

+294
-92
lines changed

13 files changed

+294
-92
lines changed

packages/dependency-installer/src/app.rs

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,101 @@
33
//! This module contains the core application logic for running the CLI.
44
55
use clap::Parser;
6+
use thiserror::Error;
67

78
use crate::cli::{Cli, Commands};
9+
use crate::handlers::check::CheckError;
10+
use crate::handlers::list::ListError;
811
use crate::DependencyManager;
912

13+
/// Exit codes for the CLI application
14+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15+
pub enum ExitCode {
16+
/// Success - all checks passed
17+
Success = 0,
18+
/// Missing dependencies (tool not installed or missing dependencies)
19+
MissingDependencies = 1,
20+
/// Invalid arguments (unknown tool name)
21+
InvalidArguments = 2,
22+
/// Internal error (detection failures or other errors)
23+
InternalError = 3,
24+
}
25+
26+
impl From<ExitCode> for i32 {
27+
fn from(code: ExitCode) -> Self {
28+
code as i32
29+
}
30+
}
31+
32+
/// Errors that can occur when running the application
33+
#[derive(Debug, Error)]
34+
pub enum AppError {
35+
/// Failed to execute the check command
36+
///
37+
/// This occurs when the check command fails to verify dependencies.
38+
#[error("Check command failed: {source}")]
39+
CheckFailed {
40+
#[source]
41+
source: CheckError,
42+
},
43+
44+
/// Failed to execute the list command
45+
///
46+
/// This occurs when the list command fails to list dependencies.
47+
#[error("List command failed: {source}")]
48+
ListFailed {
49+
#[source]
50+
source: ListError,
51+
},
52+
}
53+
54+
impl AppError {
55+
/// Convert the error to an appropriate exit code for the CLI
56+
///
57+
/// # Exit Codes
58+
///
59+
/// - `ExitCode::MissingDependencies`: Tool not installed or missing dependencies
60+
/// - `ExitCode::InvalidArguments`: Unknown tool name
61+
/// - `ExitCode::InternalError`: Detection failures or other errors
62+
#[must_use]
63+
pub fn to_exit_code(&self) -> ExitCode {
64+
use crate::handlers::check::{
65+
CheckAllToolsError, CheckError, CheckSpecificToolError, ParseToolNameError,
66+
};
67+
68+
match self {
69+
Self::CheckFailed { source } => match source {
70+
CheckError::CheckAllFailed { source } => match source {
71+
CheckAllToolsError::MissingDependencies { .. } => ExitCode::MissingDependencies,
72+
CheckAllToolsError::DependencyCheckFailed { .. } => ExitCode::InternalError,
73+
},
74+
CheckError::CheckSpecificFailed { source } => match source {
75+
CheckSpecificToolError::ParseFailed {
76+
source: ParseToolNameError::UnknownTool { .. },
77+
} => ExitCode::InvalidArguments,
78+
CheckSpecificToolError::ToolNotInstalled { .. } => {
79+
ExitCode::MissingDependencies
80+
}
81+
CheckSpecificToolError::DetectionFailed { .. } => ExitCode::InternalError,
82+
},
83+
},
84+
Self::ListFailed { .. } => ExitCode::InternalError,
85+
}
86+
}
87+
}
88+
89+
impl From<CheckError> for AppError {
90+
fn from(source: CheckError) -> Self {
91+
Self::CheckFailed { source }
92+
}
93+
}
94+
95+
impl From<ListError> for AppError {
96+
fn from(source: ListError) -> Self {
97+
Self::ListFailed { source }
98+
}
99+
}
100+
10101
/// Run the CLI application
11102
///
12103
/// # Errors
@@ -15,7 +106,7 @@ use crate::DependencyManager;
15106
/// - Dependencies are missing
16107
/// - Invalid tool name is provided
17108
/// - Internal error occurs during dependency checking
18-
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
109+
pub fn run() -> Result<(), AppError> {
19110
let cli = Cli::parse();
20111

21112
// Initialize tracing based on verbose flag
@@ -29,7 +120,9 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
29120
let manager = DependencyManager::new();
30121

31122
match cli.command {
32-
Commands::Check { tool } => crate::handlers::check::handle_check(&manager, tool),
33-
Commands::List => crate::handlers::list::handle_list(&manager),
123+
Commands::Check { tool } => crate::handlers::check::handle_check(&manager, tool)?,
124+
Commands::List => crate::handlers::list::handle_list(&manager)?,
34125
}
126+
127+
Ok(())
35128
}

packages/dependency-installer/src/bin/dependency-installer.rs

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,16 @@
1212
1313
use std::process;
1414

15-
use torrust_dependency_installer::app;
15+
use torrust_dependency_installer::app::{self, ExitCode};
1616

1717
fn main() {
1818
let exit_code = match app::run() {
19-
Ok(()) => 0,
19+
Ok(()) => ExitCode::Success,
2020
Err(e) => {
2121
eprintln!("Error: {e}");
22-
error_to_exit_code(e.as_ref())
22+
e.to_exit_code()
2323
}
2424
};
2525

26-
process::exit(exit_code);
27-
}
28-
29-
/// Determine exit code based on error type
30-
///
31-
/// # Exit Codes
32-
///
33-
/// - 1: Missing dependencies (contains "not installed" or "Missing")
34-
/// - 2: Invalid arguments (contains "Unknown tool" or "invalid")
35-
/// - 3: Internal error (all other errors)
36-
fn error_to_exit_code(error: &dyn std::error::Error) -> i32 {
37-
let error_msg = error.to_string();
38-
if error_msg.contains("not installed") || error_msg.contains("Missing") {
39-
1 // Missing dependency
40-
} else if error_msg.contains("Unknown tool") || error_msg.contains("invalid") {
41-
2 // Invalid argument
42-
} else {
43-
3 // Internal error
44-
}
26+
process::exit(exit_code.into());
4527
}

packages/dependency-installer/src/command.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
use std::process::Command;
22

3-
use crate::errors::CommandError;
3+
use thiserror::Error;
4+
5+
/// Error types for command execution utilities
6+
#[derive(Debug, Error)]
7+
pub enum CommandError {
8+
#[error("Failed to execute command '{command}': {source}")]
9+
ExecutionFailed {
10+
command: String,
11+
#[source]
12+
source: std::io::Error,
13+
},
14+
15+
#[error("Command '{command}' not found in PATH")]
16+
CommandNotFound { command: String },
17+
}
418

519
/// Check if a command exists in the system PATH
620
///

packages/dependency-installer/src/detector/ansible.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use tracing::info;
22

33
use crate::command::command_exists;
4-
use crate::detector::ToolDetector;
5-
use crate::errors::DetectionError;
4+
5+
use super::{DetectionError, ToolDetector};
66

77
/// Detector for `Ansible` tool
88
pub struct AnsibleDetector;

packages/dependency-installer/src/detector/cargo_machete.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use tracing::info;
22

33
use crate::command::command_exists;
4-
use crate::detector::ToolDetector;
5-
use crate::errors::DetectionError;
4+
5+
use super::{DetectionError, ToolDetector};
66

77
/// Detector for `cargo-machete` tool
88
pub struct CargoMacheteDetector;

packages/dependency-installer/src/detector/lxd.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use tracing::info;
22

33
use crate::command::command_exists;
4-
use crate::detector::ToolDetector;
5-
use crate::errors::DetectionError;
4+
5+
use super::{DetectionError, ToolDetector};
66

77
/// Detector for `LXD` tool
88
pub struct LxdDetector;

packages/dependency-installer/src/detector/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,27 @@ pub mod cargo_machete;
33
pub mod lxd;
44
pub mod opentofu;
55

6-
use crate::errors::DetectionError;
6+
use thiserror::Error;
77

88
pub use ansible::AnsibleDetector;
99
pub use cargo_machete::CargoMacheteDetector;
1010
pub use lxd::LxdDetector;
1111
pub use opentofu::OpenTofuDetector;
1212

13+
/// Error types for detection operations
14+
#[derive(Debug, Error)]
15+
pub enum DetectionError {
16+
#[error("Failed to detect tool '{tool}': {source}")]
17+
DetectionFailed {
18+
tool: String,
19+
#[source]
20+
source: std::io::Error,
21+
},
22+
23+
#[error("Command execution failed for tool '{tool}': {message}")]
24+
CommandFailed { tool: String, message: String },
25+
}
26+
1327
/// Trait for detecting if a tool is installed
1428
pub trait ToolDetector {
1529
/// Get the tool name for display purposes

packages/dependency-installer/src/detector/opentofu.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use tracing::info;
22

33
use crate::command::command_exists;
4-
use crate::detector::ToolDetector;
5-
use crate::errors::DetectionError;
4+
5+
use super::{DetectionError, ToolDetector};
66

77
/// Detector for `OpenTofu` tool
88
pub struct OpenTofuDetector;

packages/dependency-installer/src/errors.rs

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)