|
1 | | -use std::collections::HashMap; |
2 | 1 | use std::path::Path; |
3 | 2 | use std::sync::OnceLock; |
4 | 3 |
|
5 | | -use owhisper_client::Provider; |
| 4 | +use serde::Deserialize; |
6 | 5 |
|
| 6 | +fn default_port() -> u16 { |
| 7 | + 3001 |
| 8 | +} |
| 9 | + |
| 10 | +fn filter_empty<'de, D>(deserializer: D) -> Result<Option<String>, D::Error> |
| 11 | +where |
| 12 | + D: serde::Deserializer<'de>, |
| 13 | +{ |
| 14 | + let s: Option<String> = Option::deserialize(deserializer)?; |
| 15 | + Ok(s.filter(|s| !s.is_empty())) |
| 16 | +} |
| 17 | + |
| 18 | +#[derive(Deserialize)] |
7 | 19 | pub struct Env { |
| 20 | + #[serde(default = "default_port")] |
8 | 21 | pub port: u16, |
| 22 | + #[serde(default, deserialize_with = "filter_empty")] |
9 | 23 | pub sentry_dsn: Option<String>, |
| 24 | + #[serde(default, deserialize_with = "filter_empty")] |
10 | 25 | pub posthog_api_key: Option<String>, |
11 | 26 | pub supabase_url: String, |
12 | | - pub openrouter_api_key: String, |
13 | | - api_keys: HashMap<Provider, String>, |
| 27 | + |
| 28 | + #[serde(flatten)] |
| 29 | + pub llm: hypr_llm_proxy::Env, |
| 30 | + #[serde(flatten)] |
| 31 | + pub stt: hypr_transcribe_proxy::Env, |
14 | 32 | } |
15 | 33 |
|
16 | 34 | static ENV: OnceLock<Env> = OnceLock::new(); |
17 | 35 |
|
18 | 36 | pub fn env() -> &'static Env { |
19 | 37 | ENV.get_or_init(|| { |
20 | 38 | let _ = dotenvy::from_path(Path::new(env!("CARGO_MANIFEST_DIR")).join(".env")); |
21 | | - Env::from_env() |
| 39 | + envy::from_env().expect("Failed to load environment") |
22 | 40 | }) |
23 | 41 | } |
24 | | - |
25 | | -impl Env { |
26 | | - fn from_env() -> Self { |
27 | | - let providers = [ |
28 | | - Provider::Deepgram, |
29 | | - Provider::AssemblyAI, |
30 | | - Provider::Soniox, |
31 | | - Provider::Fireworks, |
32 | | - Provider::OpenAI, |
33 | | - Provider::Gladia, |
34 | | - ]; |
35 | | - let api_keys: HashMap<Provider, String> = providers |
36 | | - .into_iter() |
37 | | - .filter_map(|p| optional(p.env_key_name()).map(|key| (p, key))) |
38 | | - .collect(); |
39 | | - |
40 | | - Self { |
41 | | - port: parse_or("PORT", 3001), |
42 | | - sentry_dsn: optional("SENTRY_DSN"), |
43 | | - posthog_api_key: optional("POSTHOG_API_KEY"), |
44 | | - supabase_url: required("SUPABASE_URL"), |
45 | | - openrouter_api_key: required("OPENROUTER_API_KEY"), |
46 | | - api_keys, |
47 | | - } |
48 | | - } |
49 | | - |
50 | | - pub fn api_keys(&self) -> HashMap<Provider, String> { |
51 | | - self.api_keys.clone() |
52 | | - } |
53 | | - |
54 | | - pub fn configured_providers(&self) -> Vec<Provider> { |
55 | | - self.api_keys.keys().copied().collect() |
56 | | - } |
57 | | - |
58 | | - pub fn log_configured_providers(&self) { |
59 | | - let providers: Vec<_> = self.configured_providers(); |
60 | | - if providers.is_empty() { |
61 | | - tracing::warn!("no_stt_providers_configured"); |
62 | | - } else { |
63 | | - let names: Vec<_> = providers.iter().map(|p| format!("{:?}", p)).collect(); |
64 | | - tracing::info!(providers = ?names, "stt_providers_configured"); |
65 | | - } |
66 | | - } |
67 | | -} |
68 | | - |
69 | | -fn required(key: &str) -> String { |
70 | | - std::env::var(key).unwrap_or_else(|_| panic!("{key} is required")) |
71 | | -} |
72 | | - |
73 | | -fn optional(key: &str) -> Option<String> { |
74 | | - std::env::var(key).ok().filter(|s| !s.is_empty()) |
75 | | -} |
76 | | - |
77 | | -fn parse_or<T: std::str::FromStr>(key: &str, default: T) -> T { |
78 | | - std::env::var(key) |
79 | | - .ok() |
80 | | - .and_then(|v| v.parse().ok()) |
81 | | - .unwrap_or(default) |
82 | | -} |
0 commit comments