/home/jgrusewski/Work/foxhunt/config/src/data_providers.rs
Line | Count | Source |
1 | | //! Data provider endpoint configuration |
2 | | //! |
3 | | //! Centralizes all hardcoded API endpoints for data providers, enabling |
4 | | //! environment-specific configurations and easy switching between dev/staging/prod. |
5 | | |
6 | | use serde::{Deserialize, Serialize}; |
7 | | |
8 | | /// Environment specification for data providers |
9 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] |
10 | | pub enum DataProviderEnvironment { |
11 | | /// Development environment with potentially mocked or sandbox endpoints |
12 | | Development, |
13 | | /// Staging environment for pre-production testing |
14 | | Staging, |
15 | | /// Production environment with live data |
16 | | Production, |
17 | | } |
18 | | |
19 | | impl DataProviderEnvironment { |
20 | | /// Detect environment from FOXHUNT_ENV environment variable |
21 | 4 | pub fn from_env() -> Self { |
22 | 4 | match std::env::var("FOXHUNT_ENV") |
23 | 4 | .unwrap_or_else(|_| "development"1 .to_string1 ()) |
24 | 4 | .to_lowercase() |
25 | 4 | .as_str() |
26 | | { |
27 | 4 | "prod" | "production" => Self::Production1 , |
28 | 3 | "staging" | "stage"2 => Self::Staging1 , |
29 | 2 | _ => Self::Development, |
30 | | } |
31 | 4 | } |
32 | | } |
33 | | |
34 | | /// Databento endpoint configuration |
35 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
36 | | pub struct DatabentoEndpoints { |
37 | | /// WebSocket URL for real-time data streaming |
38 | | pub websocket_url: String, |
39 | | /// HTTP base URL for historical data queries |
40 | | pub historical_base_url: String, |
41 | | } |
42 | | |
43 | | impl DatabentoEndpoints { |
44 | | /// Create configuration from environment variables with fallback to defaults |
45 | 3 | pub fn from_env(environment: DataProviderEnvironment) -> Self { |
46 | 3 | let (ws_default, http_default) = match environment { |
47 | 3 | DataProviderEnvironment::Development | DataProviderEnvironment::Production => ( |
48 | 3 | "wss://gateway.databento.com/v0/subscribe", |
49 | 3 | "https://hist.databento.com", |
50 | 3 | ), |
51 | 0 | DataProviderEnvironment::Staging => ( |
52 | 0 | "wss://staging-gateway.databento.com/v0/subscribe", |
53 | 0 | "https://staging-hist.databento.com", |
54 | 0 | ), |
55 | | }; |
56 | | |
57 | | Self { |
58 | 3 | websocket_url: std::env::var("DATABENTO_WS_URL") |
59 | 3 | .unwrap_or_else(|_| ws_default2 .to_string2 ()), |
60 | 3 | historical_base_url: std::env::var("DATABENTO_HTTP_URL") |
61 | 3 | .unwrap_or_else(|_| http_default.to_string()), |
62 | | } |
63 | 3 | } |
64 | | } |
65 | | |
66 | | impl Default for DatabentoEndpoints { |
67 | 0 | fn default() -> Self { |
68 | 0 | Self::from_env(DataProviderEnvironment::from_env()) |
69 | 0 | } |
70 | | } |
71 | | |
72 | | /// Benzinga endpoint configuration |
73 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
74 | | pub struct BenzingaEndpoints { |
75 | | /// WebSocket URL for real-time news and sentiment streaming |
76 | | pub websocket_url: String, |
77 | | /// HTTP base URL for API queries |
78 | | pub api_base_url: String, |
79 | | } |
80 | | |
81 | | impl BenzingaEndpoints { |
82 | | /// Create configuration from environment variables with fallback to defaults |
83 | 2 | pub fn from_env(environment: DataProviderEnvironment) -> Self { |
84 | 2 | let (ws_default, api_default) = match environment { |
85 | 2 | DataProviderEnvironment::Development | DataProviderEnvironment::Production => ( |
86 | 2 | "wss://api.benzinga.com/api/v1/stream", |
87 | 2 | "https://api.benzinga.com/api/v2", |
88 | 2 | ), |
89 | 0 | DataProviderEnvironment::Staging => ( |
90 | 0 | "wss://staging-api.benzinga.com/api/v1/stream", |
91 | 0 | "https://staging-api.benzinga.com/api/v2", |
92 | 0 | ), |
93 | | }; |
94 | | |
95 | | Self { |
96 | 2 | websocket_url: std::env::var("BENZINGA_WS_URL") |
97 | 2 | .unwrap_or_else(|_| ws_default.to_string()), |
98 | 2 | api_base_url: std::env::var("BENZINGA_API_URL") |
99 | 2 | .unwrap_or_else(|_| api_default.to_string()), |
100 | | } |
101 | 2 | } |
102 | | } |
103 | | |
104 | | impl Default for BenzingaEndpoints { |
105 | 0 | fn default() -> Self { |
106 | 0 | Self::from_env(DataProviderEnvironment::from_env()) |
107 | 0 | } |
108 | | } |
109 | | |
110 | | /// Alpaca endpoint configuration |
111 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
112 | | pub struct AlpacaEndpoints { |
113 | | /// Base URL for trading operations (paper or live) |
114 | | pub trading_base_url: String, |
115 | | /// Base URL for market data queries |
116 | | pub data_base_url: String, |
117 | | } |
118 | | |
119 | | impl AlpacaEndpoints { |
120 | | /// Create configuration from environment variables with fallback to defaults |
121 | 3 | pub fn from_env(environment: DataProviderEnvironment) -> Self { |
122 | 3 | let (trading_default, data_default) = match environment { |
123 | 1 | DataProviderEnvironment::Development => ( |
124 | 1 | "https://paper-api.alpaca.markets", |
125 | 1 | "https://data.alpaca.markets", |
126 | 1 | ), |
127 | 0 | DataProviderEnvironment::Staging => ( |
128 | 0 | "https://paper-api.alpaca.markets", |
129 | 0 | "https://data.alpaca.markets", |
130 | 0 | ), |
131 | 2 | DataProviderEnvironment::Production => ( |
132 | 2 | "https://api.alpaca.markets", |
133 | 2 | "https://data.alpaca.markets", |
134 | 2 | ), |
135 | | }; |
136 | | |
137 | | Self { |
138 | 3 | trading_base_url: std::env::var("ALPACA_TRADING_URL") |
139 | 3 | .unwrap_or_else(|_| trading_default.to_string()), |
140 | 3 | data_base_url: std::env::var("ALPACA_DATA_URL") |
141 | 3 | .unwrap_or_else(|_| data_default.to_string()), |
142 | | } |
143 | 3 | } |
144 | | } |
145 | | |
146 | | impl Default for AlpacaEndpoints { |
147 | 0 | fn default() -> Self { |
148 | 0 | Self::from_env(DataProviderEnvironment::from_env()) |
149 | 0 | } |
150 | | } |
151 | | |
152 | | /// Interactive Brokers Gateway configuration |
153 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
154 | | pub struct IBGatewayConfig { |
155 | | /// Gateway host (typically localhost for local TWS/Gateway) |
156 | | pub host: String, |
157 | | /// Gateway port (7497 for paper trading, 7496 for live, 4001 for IB Gateway) |
158 | | pub port: u16, |
159 | | } |
160 | | |
161 | | impl IBGatewayConfig { |
162 | | /// Create configuration from environment variables with fallback to defaults |
163 | 3 | pub fn from_env(environment: DataProviderEnvironment) -> Self { |
164 | 3 | let (host_default, port_default) = match environment { |
165 | 1 | DataProviderEnvironment::Development => ("127.0.0.1", 7497), // Paper trading |
166 | 0 | DataProviderEnvironment::Staging => ("127.0.0.1", 7497), // Paper trading |
167 | 2 | DataProviderEnvironment::Production => ("127.0.0.1", 7496), // Live trading |
168 | | }; |
169 | | |
170 | | Self { |
171 | 3 | host: std::env::var("IB_GATEWAY_HOST") |
172 | 3 | .unwrap_or_else(|_| host_default.to_string()), |
173 | 3 | port: std::env::var("IB_GATEWAY_PORT") |
174 | 3 | .ok() |
175 | 3 | .and_then(|s| s.parse()0 .ok0 ()) |
176 | 3 | .unwrap_or(port_default), |
177 | | } |
178 | 3 | } |
179 | | } |
180 | | |
181 | | impl Default for IBGatewayConfig { |
182 | 0 | fn default() -> Self { |
183 | 0 | Self::from_env(DataProviderEnvironment::from_env()) |
184 | 0 | } |
185 | | } |
186 | | |
187 | | /// Master configuration for all data provider endpoints |
188 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
189 | | pub struct DataProviderConfig { |
190 | | /// Current environment |
191 | | pub environment: DataProviderEnvironment, |
192 | | /// Databento endpoints |
193 | | pub databento: DatabentoEndpoints, |
194 | | /// Benzinga endpoints |
195 | | pub benzinga: BenzingaEndpoints, |
196 | | /// Alpaca endpoints |
197 | | pub alpaca: AlpacaEndpoints, |
198 | | /// Interactive Brokers Gateway configuration |
199 | | pub ib_gateway: IBGatewayConfig, |
200 | | } |
201 | | |
202 | | impl DataProviderConfig { |
203 | | /// Create configuration from environment |
204 | 0 | pub fn from_env() -> Self { |
205 | 0 | let environment = DataProviderEnvironment::from_env(); |
206 | 0 | Self { |
207 | 0 | databento: DatabentoEndpoints::from_env(environment), |
208 | 0 | benzinga: BenzingaEndpoints::from_env(environment), |
209 | 0 | alpaca: AlpacaEndpoints::from_env(environment), |
210 | 0 | ib_gateway: IBGatewayConfig::from_env(environment), |
211 | 0 | environment, |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | /// Create configuration for specific environment |
216 | 1 | pub fn for_environment(environment: DataProviderEnvironment) -> Self { |
217 | 1 | Self { |
218 | 1 | databento: DatabentoEndpoints::from_env(environment), |
219 | 1 | benzinga: BenzingaEndpoints::from_env(environment), |
220 | 1 | alpaca: AlpacaEndpoints::from_env(environment), |
221 | 1 | ib_gateway: IBGatewayConfig::from_env(environment), |
222 | 1 | environment, |
223 | 1 | } |
224 | 1 | } |
225 | | } |
226 | | |
227 | | impl Default for DataProviderConfig { |
228 | 0 | fn default() -> Self { |
229 | 0 | Self::from_env() |
230 | 0 | } |
231 | | } |
232 | | |
233 | | #[cfg(test)] |
234 | | mod tests { |
235 | | use super::*; |
236 | | |
237 | | #[test] |
238 | 1 | fn test_environment_detection() { |
239 | 1 | std::env::set_var("FOXHUNT_ENV", "production"); |
240 | 1 | assert_eq!( |
241 | 1 | DataProviderEnvironment::from_env(), |
242 | | DataProviderEnvironment::Production |
243 | | ); |
244 | | |
245 | 1 | std::env::set_var("FOXHUNT_ENV", "staging"); |
246 | 1 | assert_eq!( |
247 | 1 | DataProviderEnvironment::from_env(), |
248 | | DataProviderEnvironment::Staging |
249 | | ); |
250 | | |
251 | 1 | std::env::set_var("FOXHUNT_ENV", "development"); |
252 | 1 | assert_eq!( |
253 | 1 | DataProviderEnvironment::from_env(), |
254 | | DataProviderEnvironment::Development |
255 | | ); |
256 | | |
257 | 1 | std::env::remove_var("FOXHUNT_ENV"); |
258 | 1 | assert_eq!( |
259 | 1 | DataProviderEnvironment::from_env(), |
260 | | DataProviderEnvironment::Development |
261 | | ); |
262 | 1 | } |
263 | | |
264 | | #[test] |
265 | 1 | fn test_databento_defaults() { |
266 | 1 | let config = DatabentoEndpoints::from_env(DataProviderEnvironment::Production); |
267 | 1 | assert_eq!( |
268 | | config.websocket_url, |
269 | | "wss://gateway.databento.com/v0/subscribe" |
270 | | ); |
271 | 1 | assert_eq!(config.historical_base_url, "https://hist.databento.com"); |
272 | 1 | } |
273 | | |
274 | | #[test] |
275 | 1 | fn test_benzinga_defaults() { |
276 | 1 | let config = BenzingaEndpoints::from_env(DataProviderEnvironment::Production); |
277 | 1 | assert_eq!( |
278 | | config.websocket_url, |
279 | | "wss://api.benzinga.com/api/v1/stream" |
280 | | ); |
281 | 1 | assert_eq!(config.api_base_url, "https://api.benzinga.com/api/v2"); |
282 | 1 | } |
283 | | |
284 | | #[test] |
285 | 1 | fn test_alpaca_defaults() { |
286 | 1 | let dev_config = AlpacaEndpoints::from_env(DataProviderEnvironment::Development); |
287 | 1 | assert_eq!( |
288 | | dev_config.trading_base_url, |
289 | | "https://paper-api.alpaca.markets" |
290 | | ); |
291 | | |
292 | 1 | let prod_config = AlpacaEndpoints::from_env(DataProviderEnvironment::Production); |
293 | 1 | assert_eq!(prod_config.trading_base_url, "https://api.alpaca.markets"); |
294 | 1 | } |
295 | | |
296 | | #[test] |
297 | 1 | fn test_ib_gateway_defaults() { |
298 | 1 | let dev_config = IBGatewayConfig::from_env(DataProviderEnvironment::Development); |
299 | 1 | assert_eq!(dev_config.host, "127.0.0.1"); |
300 | 1 | assert_eq!(dev_config.port, 7497); |
301 | | |
302 | 1 | let prod_config = IBGatewayConfig::from_env(DataProviderEnvironment::Production); |
303 | 1 | assert_eq!(prod_config.port, 7496); |
304 | 1 | } |
305 | | |
306 | | #[test] |
307 | 1 | fn test_environment_variable_override() { |
308 | 1 | std::env::set_var("DATABENTO_WS_URL", "wss://custom.databento.com"); |
309 | 1 | let config = DatabentoEndpoints::from_env(DataProviderEnvironment::Production); |
310 | 1 | assert_eq!(config.websocket_url, "wss://custom.databento.com"); |
311 | 1 | std::env::remove_var("DATABENTO_WS_URL"); |
312 | 1 | } |
313 | | |
314 | | #[test] |
315 | 1 | fn test_master_config() { |
316 | 1 | let config = DataProviderConfig::for_environment(DataProviderEnvironment::Production); |
317 | 1 | assert_eq!(config.environment, DataProviderEnvironment::Production); |
318 | 1 | assert!(!config.databento.websocket_url.is_empty()); |
319 | 1 | assert!(!config.benzinga.websocket_url.is_empty()); |
320 | 1 | assert!(!config.alpaca.trading_base_url.is_empty()); |
321 | 1 | assert!(!config.ib_gateway.host.is_empty()); |
322 | 1 | } |
323 | | } |