1use std::error::Error;
4
5use miette::Diagnostic;
6use soar_config::error::ConfigError;
7use soar_utils::error::{FileSystemError, HashError, PathError};
8use thiserror::Error;
9
10#[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
166pub 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}