1#![expect(unused_assignments)]
4
5use std::error::Error;
6
7use miette::Diagnostic;
8use soar_config::error::ConfigError;
9use soar_utils::error::{FileSystemError, HashError, PathError};
10use thiserror::Error;
11
12#[derive(Error, Diagnostic, Debug)]
14pub enum SoarError {
15 #[error(transparent)]
16 #[diagnostic(transparent)]
17 Config(#[from] ConfigError),
18
19 #[error(transparent)]
20 #[diagnostic(code(soar::system), help("Check system permissions and resources"))]
21 Errno(#[from] nix::errno::Errno),
22
23 #[error(transparent)]
24 #[diagnostic(
25 code(soar::env_var),
26 help("Set the required environment variable before running")
27 )]
28 VarError(#[from] std::env::VarError),
29
30 #[error(transparent)]
31 #[diagnostic(transparent)]
32 FileSystemError(#[from] FileSystemError),
33
34 #[error(transparent)]
35 #[diagnostic(transparent)]
36 HashError(#[from] HashError),
37
38 #[error(transparent)]
39 #[diagnostic(transparent)]
40 PathError(#[from] PathError),
41
42 #[error("Error while {action}")]
43 #[diagnostic(code(soar::io), help("Check file permissions and disk space"))]
44 IoError {
45 action: String,
46 #[source]
47 source: std::io::Error,
48 },
49
50 #[error(transparent)]
51 #[diagnostic(code(soar::time))]
52 SystemTimeError(#[from] std::time::SystemTimeError),
53
54 #[error(transparent)]
55 #[diagnostic(code(soar::toml), help("Check your configuration syntax"))]
56 TomlError(#[from] toml::ser::Error),
57
58 #[error("Database operation failed: {0}")]
59 #[diagnostic(
60 code(soar::database),
61 help("Try running 'soar sync' to refresh the database")
62 )]
63 DatabaseError(String),
64
65 #[error(transparent)]
66 #[diagnostic(
67 code(soar::network),
68 help("Check your internet connection and try again")
69 )]
70 UreqError(#[from] ureq::Error),
71
72 #[error(transparent)]
73 #[diagnostic(transparent)]
74 DownloadError(#[from] soar_dl::error::DownloadError),
75
76 #[error(transparent)]
77 #[diagnostic(transparent)]
78 PackageError(#[from] soar_package::PackageError),
79
80 #[error("Package integration failed: {0}")]
81 #[diagnostic(
82 code(soar::integration),
83 help("Check if the package format is supported")
84 )]
85 PackageIntegrationFailed(String),
86
87 #[error("Package '{0}' not found")]
88 #[diagnostic(
89 code(soar::package_not_found),
90 help("Run 'soar sync' to update package list, or check the package name")
91 )]
92 PackageNotFound(String),
93
94 #[error("Failed to fetch from remote source: {0}")]
95 #[diagnostic(
96 code(soar::fetch),
97 help("Check your internet connection and repository URL")
98 )]
99 FailedToFetchRemote(String),
100
101 #[error("Invalid path specified")]
102 #[diagnostic(
103 code(soar::invalid_path),
104 help("Provide a valid file or directory path")
105 )]
106 InvalidPath,
107
108 #[error("Thread lock poison error")]
109 #[diagnostic(
110 code(soar::poison),
111 help("This is an internal error, please report it")
112 )]
113 PoisonError,
114
115 #[error("Invalid checksum detected")]
116 #[diagnostic(
117 code(soar::checksum),
118 help("The downloaded file may be corrupted. Try downloading again.")
119 )]
120 InvalidChecksum,
121
122 #[error("Invalid package query: {0}")]
123 #[diagnostic(
124 code(soar::invalid_query),
125 help("Use format: name#pkg_id@version:repo (e.g., 'curl', 'curl#bin', 'curl@8.0.0')")
126 )]
127 InvalidPackageQuery(String),
128
129 #[error("{0}")]
130 #[diagnostic(code(soar::error))]
131 Custom(String),
132
133 #[error("{0}")]
134 #[diagnostic(code(soar::warning), severity(warning))]
135 Warning(String),
136
137 #[error(transparent)]
138 #[diagnostic(code(soar::regex), help("Check your regex pattern syntax"))]
139 RegexError(#[from] regex::Error),
140
141 #[error("Landlock is not supported on this system")]
142 #[diagnostic(
143 code(soar::sandbox::not_supported),
144 help("Landlock requires Linux kernel 5.13+. Hooks will run without sandboxing.")
145 )]
146 SandboxNotSupported,
147
148 #[error("Failed to create Landlock ruleset: {0}")]
149 #[diagnostic(
150 code(soar::sandbox::ruleset),
151 help("This may indicate a kernel or permission issue")
152 )]
153 SandboxRulesetCreation(String),
154
155 #[error("Failed to add sandbox rule for path '{path}': {reason}")]
156 #[diagnostic(
157 code(soar::sandbox::path_rule),
158 help("Check if the path exists and is accessible")
159 )]
160 SandboxPathRule { path: String, reason: String },
161
162 #[error("Failed to add sandbox network rule for port {port}: {reason}")]
163 #[diagnostic(
164 code(soar::sandbox::network_rule),
165 help("Network restrictions require Landlock V4+ (kernel 6.7+)")
166 )]
167 SandboxNetworkRule { port: u16, reason: String },
168
169 #[error("Failed to enforce Landlock sandbox: {0}")]
170 #[diagnostic(
171 code(soar::sandbox::enforcement),
172 help("This may indicate a kernel or permission issue")
173 )]
174 SandboxEnforcement(String),
175
176 #[error("Sandboxed command execution failed: {0}")]
177 #[diagnostic(
178 code(soar::sandbox::execution),
179 help("Check the command and sandbox configuration")
180 )]
181 SandboxExecution(String),
182}
183
184impl SoarError {
185 pub fn message(&self) -> String {
186 self.to_string()
187 }
188
189 pub fn root_cause(&self) -> String {
190 match self {
191 Self::UreqError(e) => {
192 format!(
193 "Root cause: {}",
194 e.source()
195 .map_or_else(|| e.to_string(), |source| source.to_string())
196 )
197 }
198 Self::Config(err) => err.to_string(),
199 _ => self.to_string(),
200 }
201 }
202}
203
204impl<T> From<std::sync::PoisonError<T>> for SoarError {
205 fn from(_: std::sync::PoisonError<T>) -> Self {
206 Self::PoisonError
207 }
208}
209
210pub trait ErrorContext<T> {
212 fn with_context<C>(self, context: C) -> std::result::Result<T, SoarError>
213 where
214 C: FnOnce() -> String;
215}
216
217impl<T> ErrorContext<T> for std::io::Result<T> {
218 fn with_context<C>(self, context: C) -> std::result::Result<T, SoarError>
219 where
220 C: FnOnce() -> String,
221 {
222 self.map_err(|err| {
223 SoarError::IoError {
224 action: context(),
225 source: err,
226 }
227 })
228 }
229}