soar_core/
error.rs

1//! Error types for soar-core.
2
3use std::error::Error;
4
5use miette::Diagnostic;
6use soar_config::error::ConfigError;
7use soar_utils::error::{FileSystemError, HashError, PathError};
8use thiserror::Error;
9
10/// Core error type for soar package manager operations.
11#[derive(Error, Diagnostic, Debug)]
12pub enum SoarError {
13    #[error(transparent)]
14    #[diagnostic(transparent)]
15    Config(#[from] ConfigError),
16
17    #[error("System error: {0}")]
18    #[diagnostic(code(soar::system), help("Check system permissions and resources"))]
19    Errno(#[from] nix::errno::Errno),
20
21    #[error("Environment variable '{0}' not set")]
22    #[diagnostic(
23        code(soar::env_var),
24        help("Set the required environment variable before running")
25    )]
26    VarError(#[from] std::env::VarError),
27
28    #[error(transparent)]
29    #[diagnostic(transparent)]
30    FileSystemError(#[from] FileSystemError),
31
32    #[error(transparent)]
33    #[diagnostic(transparent)]
34    HashError(#[from] HashError),
35
36    #[error(transparent)]
37    #[diagnostic(transparent)]
38    PathError(#[from] PathError),
39
40    #[error("IO error while {action}")]
41    #[diagnostic(code(soar::io), help("Check file permissions and disk space"))]
42    IoError {
43        action: String,
44        #[source]
45        source: std::io::Error,
46    },
47
48    #[error("System time error: {0}")]
49    #[diagnostic(code(soar::time))]
50    SystemTimeError(#[from] std::time::SystemTimeError),
51
52    #[error(transparent)]
53    #[diagnostic(code(soar::toml), help("Check your configuration syntax"))]
54    TomlError(#[from] toml::ser::Error),
55
56    #[error("Database operation failed: {0}")]
57    #[diagnostic(
58        code(soar::database),
59        help("Try running 'soar sync' to refresh the database")
60    )]
61    DatabaseError(String),
62
63    #[error("HTTP request failed")]
64    #[diagnostic(
65        code(soar::network),
66        help("Check your internet connection and try again")
67    )]
68    UreqError(#[from] ureq::Error),
69
70    #[error(transparent)]
71    #[diagnostic(transparent)]
72    DownloadError(#[from] soar_dl::error::DownloadError),
73
74    #[error(transparent)]
75    #[diagnostic(transparent)]
76    PackageError(#[from] soar_package::PackageError),
77
78    #[error("Package integration failed: {0}")]
79    #[diagnostic(
80        code(soar::integration),
81        help("Check if the package format is supported")
82    )]
83    PackageIntegrationFailed(String),
84
85    #[error("Package '{0}' not found")]
86    #[diagnostic(
87        code(soar::package_not_found),
88        help("Run 'soar sync' to update package list, or check the package name")
89    )]
90    PackageNotFound(String),
91
92    #[error("Failed to fetch from remote source: {0}")]
93    #[diagnostic(
94        code(soar::fetch),
95        help("Check your internet connection and repository URL")
96    )]
97    FailedToFetchRemote(String),
98
99    #[error("Invalid path specified")]
100    #[diagnostic(
101        code(soar::invalid_path),
102        help("Provide a valid file or directory path")
103    )]
104    InvalidPath,
105
106    #[error("Thread lock poison error")]
107    #[diagnostic(
108        code(soar::poison),
109        help("This is an internal error, please report it")
110    )]
111    PoisonError,
112
113    #[error("Invalid checksum detected")]
114    #[diagnostic(
115        code(soar::checksum),
116        help("The downloaded file may be corrupted. Try downloading again.")
117    )]
118    InvalidChecksum,
119
120    #[error("Invalid package query: {0}")]
121    #[diagnostic(
122        code(soar::invalid_query),
123        help("Use format: name#pkg_id@version:repo (e.g., 'curl', 'curl#bin', 'curl@8.0.0')")
124    )]
125    InvalidPackageQuery(String),
126
127    #[error("{0}")]
128    #[diagnostic(code(soar::error))]
129    Custom(String),
130
131    #[error("{0}")]
132    #[diagnostic(code(soar::warning), severity(warning))]
133    Warning(String),
134
135    #[error("Regex compilation error: {0}")]
136    #[diagnostic(code(soar::regex), help("Check your regex pattern syntax"))]
137    RegexError(#[from] regex::Error),
138}
139
140impl SoarError {
141    pub fn message(&self) -> String {
142        self.to_string()
143    }
144
145    pub fn root_cause(&self) -> String {
146        match self {
147            Self::UreqError(e) => {
148                format!(
149                    "Root cause: {}",
150                    e.source()
151                        .map_or_else(|| e.to_string(), |source| source.to_string())
152                )
153            }
154            Self::Config(err) => err.to_string(),
155            _ => self.to_string(),
156        }
157    }
158}
159
160impl<T> From<std::sync::PoisonError<T>> for SoarError {
161    fn from(_: std::sync::PoisonError<T>) -> Self {
162        Self::PoisonError
163    }
164}
165
166/// Trait for adding context to IO errors.
167pub trait ErrorContext<T> {
168    fn with_context<C>(self, context: C) -> std::result::Result<T, SoarError>
169    where
170        C: FnOnce() -> String;
171}
172
173impl<T> ErrorContext<T> for std::io::Result<T> {
174    fn with_context<C>(self, context: C) -> std::result::Result<T, SoarError>
175    where
176        C: FnOnce() -> String,
177    {
178        self.map_err(|err| {
179            SoarError::IoError {
180                action: context(),
181                source: err,
182            }
183        })
184    }
185}