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}
30
31#[derive(Debug, Clone, Deserialize)]
33pub struct NvdConfig {
34 pub requests_per_window: Option<u32>,
37 pub window_seconds: Option<u64>,
40 pub max_results: Option<u32>,
43 pub max_days_range: Option<i64>,
46}
47
48impl Default for NvdConfig {
49 fn default() -> Self {
50 Self {
51 requests_per_window: None, window_seconds: Some(30),
53 max_results: None,
54 max_days_range: Some(120),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Deserialize)]
61pub struct OssIndexConfig {
62 pub user: Option<String>,
64 pub token: Option<String>,
66 #[serde(default = "default_ossindex_batch_size")]
68 pub batch_size: usize,
69}
70
71fn default_ossindex_batch_size() -> usize {
72 128
73}
74
75impl Default for OssIndexConfig {
76 fn default() -> Self {
77 Self {
78 user: None,
79 token: None,
80 batch_size: 128,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Deserialize)]
87pub struct StoreConfig {
88 pub ttl_seconds: Option<u64>,
90 #[serde(default = "default_compression_level")]
92 pub compression_level: i32,
93 #[serde(default = "default_key_prefix")]
95 pub key_prefix: String,
96}
97
98fn default_compression_level() -> i32 {
99 3
100}
101
102fn default_key_prefix() -> String {
103 "vuln".to_string()
104}
105
106impl Default for StoreConfig {
107 fn default() -> Self {
108 Self {
109 ttl_seconds: None,
110 compression_level: 3,
111 key_prefix: "vuln".to_string(),
112 }
113 }
114}
115
116impl Config {
117 pub fn from_env() -> Result<Self> {
132 dotenv().ok();
133
134 let ghsa_token = env::var("VULNERA__APIS__GHSA__TOKEN").ok();
135 let nvd_api_key = env::var("VULNERA__APIS__NVD__API_KEY").ok();
136
137 let redis_url =
138 env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
139
140 let ossindex = {
141 let user = env::var("OSSINDEX_USER").ok();
142 let token = env::var("OSSINDEX_TOKEN").ok();
143 if user.is_some() || token.is_some() {
144 Some(OssIndexConfig {
145 user,
146 token,
147 batch_size: 128,
148 })
149 } else {
150 None
151 }
152 };
153
154 let ttl_seconds = env::var("VULNERA__STORE__TTL_SECONDS")
155 .ok()
156 .and_then(|s| s.parse().ok());
157
158 let nvd = NvdConfig {
159 requests_per_window: env::var("VULNERA__NVD__REQUESTS_PER_WINDOW")
160 .ok()
161 .and_then(|s| s.parse().ok()),
162 window_seconds: env::var("VULNERA__NVD__WINDOW_SECONDS")
163 .ok()
164 .and_then(|s| s.parse().ok()),
165 max_results: env::var("VULNERA__NVD__MAX_RESULTS")
166 .ok()
167 .and_then(|s| s.parse().ok()),
168 max_days_range: Some(120),
169 };
170
171 let store = StoreConfig {
172 ttl_seconds,
173 compression_level: env::var("VULNERA__STORE__COMPRESSION_LEVEL")
174 .ok()
175 .and_then(|s| s.parse().ok())
176 .unwrap_or(3),
177 key_prefix: env::var("VULNERA__STORE__KEY_PREFIX")
178 .unwrap_or_else(|_| "vuln".to_string()),
179 };
180
181 Ok(Self {
182 ghsa_token,
183 nvd_api_key,
184 redis_url,
185 ossindex,
186 nvd,
187 store,
188 })
189 }
190
191 pub fn for_testing(redis_url: &str) -> Self {
193 Self {
194 ghsa_token: None,
195 nvd_api_key: None,
196 redis_url: redis_url.to_string(),
197 ossindex: None,
198 nvd: NvdConfig::default(),
199 store: StoreConfig::default(),
200 }
201 }
202
203 pub fn validate_for_ghsa(&self) -> Result<&str> {
205 self.ghsa_token.as_deref().ok_or_else(|| {
206 AdvisoryError::config("GHSA token is required (set VULNERA__APIS__GHSA__TOKEN)")
207 })
208 }
209}