vulnera_advisor/
error.rs

1//! Error types for the vulnera-advisors crate.
2//!
3//! This module provides a comprehensive error type [`AdvisoryError`] that covers
4//! all failure modes in the library, enabling proper error handling.
5
6use std::io;
7
8/// The main error type for all operations in this crate.
9#[derive(Debug, thiserror::Error)]
10pub enum AdvisoryError {
11    /// Redis/DragonflyDB connection or operation failed.
12    #[error("Redis error: {0}")]
13    Redis(#[from] redis::RedisError),
14
15    /// Failed to fetch data from an advisory source.
16    #[error("Source '{source_name}' fetch failed: {message}")]
17    SourceFetch {
18        /// Name of the source that failed (e.g., "GHSA", "NVD", "OSV").
19        source_name: String,
20        /// Description of what went wrong.
21        message: String,
22    },
23
24    /// Configuration error (missing or invalid values).
25    #[error("Configuration error: {0}")]
26    Config(String),
27
28    /// JSON serialization/deserialization failed.
29    #[error("Serialization error: {0}")]
30    Serialization(#[from] serde_json::Error),
31
32    /// Compression or decompression failed.
33    #[error("Compression error: {0}")]
34    Compression(String),
35
36    /// HTTP request failed.
37    #[error("HTTP error: {0}")]
38    Http(#[from] reqwest::Error),
39
40    /// HTTP request via middleware failed.
41    #[error("HTTP middleware error: {0}")]
42    HttpMiddleware(#[from] reqwest_middleware::Error),
43
44    /// Rate limit exceeded.
45    #[error("Rate limit exceeded for source '{source_name}': {message}")]
46    RateLimit {
47        /// Name of the source that hit rate limits.
48        source_name: String,
49        /// Additional details about the rate limit.
50        message: String,
51    },
52
53    /// I/O error (file operations, etc.).
54    #[error("I/O error: {0}")]
55    Io(io::Error),
56
57    /// Version parsing failed.
58    #[error("Invalid version '{version}': {message}")]
59    VersionParse {
60        /// The version string that failed to parse.
61        version: String,
62        /// Why parsing failed.
63        message: String,
64    },
65
66    /// ZIP archive error.
67    #[error("ZIP error: {0}")]
68    Zip(#[from] zip::result::ZipError),
69
70    /// Task join error (from spawned tasks).
71    #[error("Task join error: {0}")]
72    TaskJoin(#[from] tokio::task::JoinError),
73
74    /// GraphQL API error.
75    #[error("GraphQL error: {0}")]
76    GraphQL(String),
77}
78
79/// A specialized Result type for advisory operations.
80pub type Result<T> = std::result::Result<T, AdvisoryError>;
81
82impl AdvisoryError {
83    /// Create a new source fetch error.
84    pub fn source_fetch(source: impl Into<String>, message: impl Into<String>) -> Self {
85        Self::SourceFetch {
86            source_name: source.into(),
87            message: message.into(),
88        }
89    }
90
91    /// Create a new configuration error.
92    pub fn config(message: impl Into<String>) -> Self {
93        Self::Config(message.into())
94    }
95
96    /// Create a new compression error.
97    pub fn compression(message: impl Into<String>) -> Self {
98        Self::Compression(message.into())
99    }
100
101    /// Create a new rate limit error.
102    pub fn rate_limit(source: impl Into<String>, message: impl Into<String>) -> Self {
103        Self::RateLimit {
104            source_name: source.into(),
105            message: message.into(),
106        }
107    }
108
109    /// Create a new version parse error.
110    pub fn version_parse(version: impl Into<String>, message: impl Into<String>) -> Self {
111        Self::VersionParse {
112            version: version.into(),
113            message: message.into(),
114        }
115    }
116
117    /// Create a new GraphQL error.
118    pub fn graphql(message: impl Into<String>) -> Self {
119        Self::GraphQL(message.into())
120    }
121
122    /// Check if this error is retryable.
123    pub fn is_retryable(&self) -> bool {
124        matches!(
125            self,
126            Self::Http(_) | Self::HttpMiddleware(_) | Self::RateLimit { .. } | Self::Redis(_)
127        )
128    }
129}
130
131// Convert from zstd errors
132impl From<std::io::Error> for AdvisoryError {
133    fn from(err: std::io::Error) -> Self {
134        // Check if it's a compression-related error
135        if err.to_string().contains("zstd") || err.to_string().contains("compress") {
136            Self::Compression(err.to_string())
137        } else {
138            Self::Io(err)
139        }
140    }
141}