Skip to main content

dry4rust/cli/
cli_error.rs

1// Copyright (c) 2026 Matjaz Domen Pecan
2// Copyright 2026 Umberto Gotti <umberto.gotti@umbertogotti.dev>
3// Licensed under the MIT License
4// SPDX-License-Identifier: MIT
5
6use std::error::Error;
7use std::fmt;
8use std::io;
9use std::path::PathBuf;
10
11use crate::error::Error as AnalysisError;
12
13/// Errors returned by CLI command functions.
14#[derive(Debug)]
15pub enum CliError {
16    /// An I/O error (exit code 2).
17    Io(io::Error),
18    /// No source files found (exit code 2).
19    NoSourceFiles(PathBuf),
20    /// No recognized source files for language auto-detection (exit code 2).
21    NoRecognizedFiles,
22    /// Multiple languages detected — user must specify `--language` (exit code 2).
23    AmbiguousLanguage(Vec<String>),
24    /// Analysis pipeline failed (exit code 2).
25    Analysis(AnalysisError),
26    /// Invalid fingerprint string (exit code 2).
27    InvalidFingerprint(String),
28    /// Configuration outside the range its field allows (exit code 2).
29    InvalidConfig(String),
30    /// Check thresholds exceeded (exit code 1).
31    CheckFailed,
32}
33
34impl CliError {
35    /// Map to an appropriate process exit code.
36    #[must_use]
37    pub const fn exit_code(&self) -> i32 {
38        match self {
39            Self::CheckFailed => 1,
40            Self::Io(_)
41            | Self::NoSourceFiles(_)
42            | Self::NoRecognizedFiles
43            | Self::AmbiguousLanguage(_)
44            | Self::Analysis(_)
45            | Self::InvalidFingerprint(_)
46            | Self::InvalidConfig(_) => 2,
47        }
48    }
49}
50
51impl fmt::Display for CliError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            Self::Io(e) => write!(f, "{e}"),
55            Self::NoSourceFiles(path) => {
56                write!(f, "No source files found in {}", path.display())
57            }
58            Self::NoRecognizedFiles => {
59                write!(
60                    f,
61                    "No recognized source files found. Use --language to specify the language."
62                )
63            }
64            Self::AmbiguousLanguage(langs) => {
65                write!(
66                    f,
67                    "Multiple languages detected: {}. Use --language to specify which to analyze.",
68                    langs.join(", ")
69                )
70            }
71            Self::Analysis(e) => write!(f, "{e}"),
72            Self::InvalidFingerprint(fp) => write!(f, "Invalid fingerprint: {fp}"),
73            Self::InvalidConfig(message) => write!(f, "{message}"),
74            Self::CheckFailed => write!(f, "Check failed"),
75        }
76    }
77}
78
79impl Error for CliError {
80    fn source(&self) -> Option<&(dyn Error + 'static)> {
81        match self {
82            Self::Io(e) => Some(e),
83            Self::Analysis(e) => Some(e),
84            _ => None,
85        }
86    }
87}
88
89impl From<io::Error> for CliError {
90    fn from(e: io::Error) -> Self {
91        Self::Io(e)
92    }
93}
94
95impl From<AnalysisError> for CliError {
96    fn from(e: AnalysisError) -> Self {
97        Self::Analysis(e)
98    }
99}
100
101/// Result type for CLI operations.
102pub type CliResult<T = ()> = Result<T, CliError>;