vulnera_advisor/
config.rs1use crate::error::{AdvisoryError, Result};
7use dotenvy::dotenv;
8use serde::Deserialize;
9use std::env;
10
11#[derive(Debug, Clone, Deserialize)]
13pub struct Config {
14 pub ghsa_token: Option<String>,
16 pub nvd_api_key: Option<String>,
18 pub redis_url: String,
20 #[serde(default)]
22 pub ossindex: Option<OssIndexConfig>,
23 #[serde(default)]
25 pub nvd: NvdConfig,
26 #[serde(default)]
28 pub store: StoreConfig,
29 #[serde(default)]
31 pub log_to_file: bool,
32 #[serde(default = "default_log_dir")]
34 pub log_dir: String,
35}
36
37fn default_log_dir() -> String {
38 "logs".to_string()
39}
40
41#[derive(Debug, Clone, Deserialize)]
43pub struct NvdConfig {
44 pub requests_per_window: Option<u32>,
47 pub window_seconds: Option<u64>,
50 pub max_results: Option<u32>,
53 pub max_days_range: Option<i64>,
56}
57
58impl Default for NvdConfig {
59 fn default() -> Self {
60 Self {
61 requests_per_window: None, window_seconds: Some(30),
63 max_results: None,
64 max_days_range: Some(120),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Deserialize)]
71pub struct OssIndexConfig {
72 pub user: Option<String>,
74 pub token: Option<String>,
76 #[serde(default = "default_ossindex_batch_size")]
78 pub batch_size: usize,
79}
80
81fn default_ossindex_batch_size() -> usize {
82 128
83}
84
85impl Default for OssIndexConfig {
86 fn default() -> Self {
87 Self {
88 user: None,
89 token: None,
90 batch_size: 128,
91 }
92 }
93}
94
95#[derive(Debug, Clone, Deserialize)]
97pub struct StoreConfig {
98 pub ttl_seconds: Option<u64>,
100 #[serde(default = "default_compression_level")]
102 pub compression_level: i32,
103 #[serde(default = "default_key_prefix")]
105 pub key_prefix: String,
106}
107
108fn default_compression_level() -> i32 {
109 3
110}
111
112fn default_key_prefix() -> String {
113 "vuln".to_string()
114}
115
116impl Default for StoreConfig {
117 fn default() -> Self {
118 Self {
119 ttl_seconds: None,
120 compression_level: 3,
121 key_prefix: "vuln".to_string(),
122 }
123 }
124}
125
126impl Config {
127 pub fn from_env() -> Result<Self> {
142 dotenv().ok();
143
144 let ghsa_token = env::var("VULNERA__APIS__GHSA__TOKEN").ok();
145 let nvd_api_key = env::var("VULNERA__APIS__NVD__API_KEY").ok();
146
147 let redis_url =
148 env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
149
150 let ossindex = {
151 let user = env::var("OSSINDEX_USER").ok();
152 let token = env::var("OSSINDEX_TOKEN").ok();
153 if user.is_some() || token.is_some() {
154 Some(OssIndexConfig {
155 user,
156 token,
157 batch_size: 128,
158 })
159 } else {
160 None
161 }
162 };
163
164 let ttl_seconds = env::var("VULNERA__STORE__TTL_SECONDS")
165 .ok()
166 .and_then(|s| s.parse().ok());
167
168 let nvd = NvdConfig {
169 requests_per_window: env::var("VULNERA__NVD__REQUESTS_PER_WINDOW")
170 .ok()
171 .and_then(|s| s.parse().ok()),
172 window_seconds: env::var("VULNERA__NVD__WINDOW_SECONDS")
173 .ok()
174 .and_then(|s| s.parse().ok()),
175 max_results: env::var("VULNERA__NVD__MAX_RESULTS")
176 .ok()
177 .and_then(|s| s.parse().ok()),
178 max_days_range: Some(120),
179 };
180
181 let store = StoreConfig {
182 ttl_seconds,
183 compression_level: env::var("VULNERA__STORE__COMPRESSION_LEVEL")
184 .ok()
185 .and_then(|s| s.parse().ok())
186 .unwrap_or(3),
187 key_prefix: env::var("VULNERA__STORE__KEY_PREFIX")
188 .unwrap_or_else(|_| "vuln".to_string()),
189 };
190
191 let log_to_file = env::var("VULNERA_LOG_TO_FILE")
192 .map(|v| v.to_lowercase() == "true")
193 .unwrap_or(false);
194
195 let log_dir = env::var("VULNERA_LOG_DIR").unwrap_or_else(|_| "logs".to_string());
196
197 Ok(Self {
198 ghsa_token,
199 nvd_api_key,
200 redis_url,
201 ossindex,
202 nvd,
203 store,
204 log_to_file,
205 log_dir,
206 })
207 }
208
209 pub fn for_testing(redis_url: &str) -> Self {
211 Self {
212 ghsa_token: None,
213 nvd_api_key: None,
214 redis_url: redis_url.to_string(),
215 ossindex: None,
216 nvd: NvdConfig::default(),
217 store: StoreConfig::default(),
218 log_to_file: false,
219 log_dir: "logs".to_string(),
220 }
221 }
222
223 pub fn validate_for_ghsa(&self) -> Result<&str> {
225 self.ghsa_token.as_deref().ok_or_else(|| {
226 AdvisoryError::config("GHSA token is required (set VULNERA__APIS__GHSA__TOKEN)")
227 })
228 }
229}