vanguards_rs/error.rs
1//! Error types for vanguards-rs.
2//!
3//! This module defines the [`enum@Error`] enum representing all possible error conditions
4//! in the vanguards-rs library. Each variant provides specific information about
5//! the failure and guidance on recovery.
6//!
7//! # Overview
8//!
9//! The error system is designed to provide:
10//!
11//! - **Specific error types** for different failure categories
12//! - **Recovery guidance** for each error type
13//! - **Seamless integration** with stem-rs errors
14//! - **Informative messages** without leaking sensitive data
15//!
16//! # Error Categories
17//!
18//! ```text
19//! Error
20//! ├── Io ◄── File/network I/O failures
21//! ├── Config ◄── Invalid configuration
22//! ├── Control ◄── Tor control protocol errors (from stem-rs)
23//! ├── State ◄── State file corruption/format issues
24//! ├── Consensus ◄── Consensus parsing failures
25//! ├── NoNodesRemain ◄── All relays filtered out
26//! ├── Validation ◄── Invalid input data
27//! └── DescriptorUnavailable ◄── Missing descriptors
28//! ```
29//!
30//! # Recovery Guide
31//!
32//! | Error | Recoverable | Retry | Recommended Action |
33//! |-------|-------------|-------|-------------------|
34//! | [`Io`](Error::Io) | Sometimes | Yes (backoff) | Check permissions, disk space |
35//! | [`Config`](Error::Config) | No | No | Fix configuration file |
36//! | [`Control`](Error::Control) | Sometimes | Yes | Reconnect to Tor |
37//! | [`State`](Error::State) | Sometimes | No | Delete state file, restart |
38//! | [`Consensus`](Error::Consensus) | Sometimes | Yes | Wait for new consensus |
39//! | [`NoNodesRemain`](Error::NoNodesRemain) | No | No | Adjust ExcludeNodes |
40//! | [`Validation`](Error::Validation) | No | No | Fix input data |
41//! | [`DescriptorUnavailable`](Error::DescriptorUnavailable) | Yes | Yes | Wait for bootstrap |
42//!
43//! # Example
44//!
45//! ## Basic Error Handling
46//!
47//! ```rust
48//! use vanguards_rs::{Config, Error, Result};
49//!
50//! fn load_config() -> Result<Config> {
51//! let config = Config::from_file(std::path::Path::new("vanguards.conf"))?;
52//! config.validate()?;
53//! Ok(config)
54//! }
55//!
56//! fn main() {
57//! match load_config() {
58//! Ok(config) => println!("Config loaded successfully"),
59//! Err(Error::Io(e)) => eprintln!("File error: {}", e),
60//! Err(Error::Config(msg)) => eprintln!("Config error: {}", msg),
61//! Err(e) => eprintln!("Other error: {}", e),
62//! }
63//! }
64//! ```
65//!
66//! ## Retry Logic
67//!
68//! ```rust,no_run
69//! use vanguards_rs::{Error, Result};
70//! use std::time::Duration;
71//!
72//! async fn with_retry<F, T>(mut f: F, max_retries: u32) -> Result<T>
73//! where
74//! F: FnMut() -> Result<T>,
75//! {
76//! let mut attempts = 0;
77//! loop {
78//! match f() {
79//! Ok(result) => return Ok(result),
80//! Err(Error::Io(_)) | Err(Error::Control(_)) if attempts < max_retries => {
81//! attempts += 1;
82//! tokio::time::sleep(Duration::from_secs(1 << attempts)).await;
83//! }
84//! Err(e) => return Err(e),
85//! }
86//! }
87//! }
88//! ```
89//!
90//! # See Also
91//!
92//! - [`Result`] - Type alias for `std::result::Result<T, Error>`
93//! - [`stem_rs::Error`] - Underlying Tor control errors
94//! - [`Config::validate`](crate::Config::validate) - Configuration validation
95
96use thiserror::Error;
97
98/// Errors that can occur during vanguards-rs operations.
99///
100/// This enum represents all possible error conditions in the library.
101/// Each variant provides specific information about the failure and
102/// guidance on recovery.
103///
104/// # Error Handling Patterns
105///
106/// ## Match on Specific Errors
107///
108/// ```rust
109/// use vanguards_rs::Error;
110///
111/// fn handle_error(err: Error) {
112/// match err {
113/// Error::Io(io_err) => {
114/// eprintln!("I/O error: {}", io_err);
115/// // Check file permissions, disk space, network
116/// }
117/// Error::Config(msg) => {
118/// eprintln!("Configuration error: {}", msg);
119/// // Fix configuration and restart
120/// }
121/// Error::Control(ctrl_err) => {
122/// eprintln!("Tor control error: {}", ctrl_err);
123/// // Reconnect to Tor
124/// }
125/// Error::State(msg) => {
126/// eprintln!("State file error: {}", msg);
127/// // Delete state file and restart
128/// }
129/// Error::Consensus(msg) => {
130/// eprintln!("Consensus error: {}", msg);
131/// // Wait for Tor to get new consensus
132/// }
133/// Error::NoNodesRemain => {
134/// eprintln!("No nodes remain after filtering");
135/// // Adjust ExcludeNodes configuration
136/// }
137/// Error::Validation(msg) => {
138/// eprintln!("Validation error: {}", msg);
139/// // Fix invalid input
140/// }
141/// Error::DescriptorUnavailable(msg) => {
142/// eprintln!("Descriptor unavailable: {}", msg);
143/// // Wait for Tor to finish bootstrapping
144/// }
145/// }
146/// }
147/// ```
148///
149/// ## Check if Retryable
150///
151/// ```rust
152/// use vanguards_rs::Error;
153///
154/// fn is_retryable(err: &Error) -> bool {
155/// matches!(err,
156/// Error::Io(_) |
157/// Error::Control(_) |
158/// Error::Consensus(_) |
159/// Error::DescriptorUnavailable(_)
160/// )
161/// }
162/// ```
163///
164/// # See Also
165///
166/// - [`Result`] - Type alias using this error type
167/// - [`stem_rs::Error`] - Underlying control protocol errors
168#[derive(Debug, Error)]
169pub enum Error {
170 /// I/O error during file or network operations.
171 ///
172 /// This error wraps standard I/O errors that occur during file operations
173 /// (reading/writing state files, config files) or network operations.
174 ///
175 /// # Recovery
176 ///
177 /// - Check file permissions and paths
178 /// - Retry with exponential backoff for transient issues
179 /// - Verify disk space for write operations
180 #[error("I/O error: {0}")]
181 Io(#[from] std::io::Error),
182
183 /// Configuration error.
184 ///
185 /// This error indicates invalid configuration values or parsing failures.
186 /// The message describes what was wrong with the configuration.
187 ///
188 /// # Recovery
189 ///
190 /// Fix the configuration file or command-line arguments. This error
191 /// is not recoverable without user intervention.
192 #[error("configuration error: {0}")]
193 Config(String),
194
195 /// Tor control protocol error.
196 ///
197 /// This error wraps errors from stem-rs when communicating with Tor's
198 /// control port.
199 ///
200 /// # Recovery
201 ///
202 /// - Check if Tor is running
203 /// - Verify control port configuration
204 /// - Retry connection with backoff
205 #[error("Tor control error: {0}")]
206 Control(#[from] stem_rs::Error),
207
208 /// State file error.
209 ///
210 /// This error indicates problems with the vanguard state file, such as
211 /// corruption, invalid format, or incompatible version.
212 ///
213 /// # Recovery
214 ///
215 /// - Delete the corrupted state file and let vanguards create a fresh one
216 /// - Check file permissions
217 /// - Verify the file wasn't modified externally
218 #[error("state file error: {0}")]
219 State(String),
220
221 /// Consensus parsing error.
222 ///
223 /// This error occurs when parsing the network consensus fails.
224 ///
225 /// # Recovery
226 ///
227 /// - Wait for a new consensus
228 /// - Verify Tor has finished bootstrapping
229 /// - Check DataDirectory configuration
230 #[error("consensus parse error: {0}")]
231 Consensus(String),
232
233 /// No nodes remain after applying restrictions.
234 ///
235 /// This error occurs when all relays are filtered out by the configured
236 /// restrictions (ExcludeNodes, flag requirements, etc.).
237 ///
238 /// # Recovery
239 ///
240 /// - Review ExcludeNodes configuration
241 /// - Reduce restrictions
242 /// - Wait for more relays to appear in consensus
243 #[error("no nodes remain after restrictions")]
244 NoNodesRemain,
245
246 /// Input validation error.
247 ///
248 /// This error indicates that input data failed validation checks.
249 ///
250 /// # Recovery
251 ///
252 /// Fix the invalid input. This error is not recoverable without
253 /// correcting the input data.
254 #[error("validation error: {0}")]
255 Validation(String),
256
257 /// Descriptor unavailable.
258 ///
259 /// This error occurs when Tor doesn't have the required descriptors
260 /// cached yet, typically during bootstrap.
261 ///
262 /// # Recovery
263 ///
264 /// - Wait for Tor to finish bootstrapping
265 /// - Retry after a short delay
266 #[error("descriptor unavailable: {0}")]
267 DescriptorUnavailable(String),
268}
269
270/// Result type alias for vanguards-rs operations.
271///
272/// This is a convenience alias for `std::result::Result<T, Error>` used
273/// throughout the vanguards-rs library.
274///
275/// # Example
276///
277/// ```rust
278/// use vanguards_rs::{Config, Result};
279///
280/// fn load_and_validate_config() -> Result<Config> {
281/// let config = Config::from_file(std::path::Path::new("vanguards.conf"))?;
282/// config.validate()?;
283/// Ok(config)
284/// }
285/// ```
286///
287/// # See Also
288///
289/// - [`enum@Error`] - The error type used in this result
290pub type Result<T> = std::result::Result<T, Error>;