Skip to main content

ras_config/domain/
config.rs

1use std::env;
2use std::path::PathBuf;
3use std::time::Duration;
4
5use ras_errors::AppError;
6use thiserror::Error;
7
8#[derive(Debug, Clone)]
9pub struct Config {
10    pub home_dir: PathBuf,
11    pub config_dir: PathBuf,
12    pub extensions_dir: PathBuf,
13    pub profiles_dir: PathBuf,
14    pub anthropic_api_key: Option<String>,
15    pub claude_code_oauth_enabled: bool,
16    pub cdp_timeout: Duration,
17    pub action_timeout: Duration,
18    pub anonymized_telemetry: bool,
19    pub cloud_sync: bool,
20}
21
22#[derive(Debug, Error)]
23pub enum ConfigError {
24    #[error("missing required env var: {0}")]
25    MissingEnv(String),
26    #[error("invalid value for {0}: {1}")]
27    InvalidValue(String, String),
28}
29
30impl From<ConfigError> for AppError {
31    fn from(e: ConfigError) -> Self {
32        Self::ValidationError(e.to_string())
33    }
34}
35
36impl Config {
37    pub fn from_env() -> Result<Self, ConfigError> {
38        let home_dir = env::var("RAS_HOME")
39            .ok()
40            .map(PathBuf::from)
41            .or_else(|| dirs_home())
42            .ok_or_else(|| ConfigError::MissingEnv("RAS_HOME".into()))?;
43        let config_dir = env::var("RAS_CONFIG_DIR")
44            .map(PathBuf::from)
45            .unwrap_or_else(|_| home_dir.join(".config").join("rust-ai-surfer"));
46        let extensions_dir = env::var("RAS_EXTENSIONS_DIR")
47            .map(PathBuf::from)
48            .unwrap_or_else(|_| config_dir.join("extensions"));
49        let profiles_dir = env::var("RAS_PROFILES_DIR")
50            .map(PathBuf::from)
51            .unwrap_or_else(|_| config_dir.join("profiles"));
52        Ok(Self {
53            home_dir,
54            config_dir,
55            extensions_dir,
56            profiles_dir,
57            anthropic_api_key: env::var("ANTHROPIC_API_KEY").ok(),
58            claude_code_oauth_enabled: env::var("RAS_CLAUDE_CODE_OAUTH").ok().as_deref()
59                != Some("0"),
60            cdp_timeout: parse_duration("RAS_CDP_TIMEOUT_S", 60)?,
61            action_timeout: parse_duration("RAS_ACTION_TIMEOUT_S", 180)?,
62            anonymized_telemetry: env::var("RAS_ANONYMIZED_TELEMETRY").ok().as_deref() != Some("0"),
63            cloud_sync: env::var("RAS_CLOUD_SYNC").ok().as_deref() == Some("1"),
64        })
65    }
66}
67
68fn parse_duration(key: &str, default_secs: u64) -> Result<Duration, ConfigError> {
69    match env::var(key) {
70        Ok(v) => v
71            .parse::<u64>()
72            .map(Duration::from_secs)
73            .map_err(|_| ConfigError::InvalidValue(key.into(), v)),
74        Err(_) => Ok(Duration::from_secs(default_secs)),
75    }
76}
77
78fn dirs_home() -> Option<PathBuf> {
79    env::var_os("HOME").map(PathBuf::from)
80}