Skip to main content

cc_switch/config/
config.rs

1use anyhow::{Context, Result};
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use crate::config::types::Configuration;
6
7/// Type alias for environment variable map
8type EnvVarMap = BTreeMap<String, String>;
9/// Type alias for environment variable tuples
10type EnvVarTuple = (String, String);
11/// Type alias for environment variable tuples vector
12type EnvVarTuples = Vec<EnvVarTuple>;
13
14/// Environment variable manager for API configuration
15///
16/// Handles setting environment variables for the Claude CLI process
17#[derive(Default, Clone)]
18pub struct EnvironmentConfig {
19    /// Environment variables to be set
20    pub env_vars: EnvVarMap,
21}
22
23impl EnvironmentConfig {
24    /// Create a new environment configuration from a Claude configuration
25    ///
26    /// # Arguments
27    /// * `config` - Configuration containing token, URL, and optional model settings
28    ///
29    /// # Returns
30    /// EnvironmentConfig with the appropriate environment variables set
31    pub fn from_config(config: &Configuration) -> Self {
32        let mut env_vars = EnvVarMap::new();
33
34        // Set required environment variables
35        env_vars.insert("ANTHROPIC_AUTH_TOKEN".to_string(), config.token.clone());
36        env_vars.insert("ANTHROPIC_BASE_URL".to_string(), config.url.clone());
37
38        // Set model configurations only if provided
39        if let Some(model) = &config.model
40            && !model.is_empty()
41        {
42            env_vars.insert("ANTHROPIC_MODEL".to_string(), model.clone());
43        }
44
45        if let Some(small_fast_model) = &config.small_fast_model
46            && !small_fast_model.is_empty()
47        {
48            env_vars.insert(
49                "ANTHROPIC_SMALL_FAST_MODEL".to_string(),
50                small_fast_model.clone(),
51            );
52        }
53
54        // Set max thinking tokens only if provided
55        if let Some(max_thinking_tokens) = config.max_thinking_tokens {
56            env_vars.insert(
57                "ANTHROPIC_MAX_THINKING_TOKENS".to_string(),
58                max_thinking_tokens.to_string(),
59            );
60        }
61
62        // Set API timeout only if provided
63        if let Some(timeout) = config.api_timeout_ms {
64            env_vars.insert("API_TIMEOUT_MS".to_string(), timeout.to_string());
65        }
66
67        // Set disable nonessential traffic flag only if provided
68        if let Some(flag) = config.claude_code_disable_nonessential_traffic {
69            env_vars.insert(
70                "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".to_string(),
71                flag.to_string(),
72            );
73        }
74
75        // Set default Sonnet model only if provided
76        if let Some(model) = &config.anthropic_default_sonnet_model
77            && !model.is_empty()
78        {
79            env_vars.insert("ANTHROPIC_DEFAULT_SONNET_MODEL".to_string(), model.clone());
80        }
81
82        // Set default Opus model only if provided
83        if let Some(model) = &config.anthropic_default_opus_model
84            && !model.is_empty()
85        {
86            env_vars.insert("ANTHROPIC_DEFAULT_OPUS_MODEL".to_string(), model.clone());
87        }
88
89        // Set default Haiku model only if provided
90        if let Some(model) = &config.anthropic_default_haiku_model
91            && !model.is_empty()
92        {
93            env_vars.insert("ANTHROPIC_DEFAULT_HAIKU_MODEL".to_string(), model.clone());
94        }
95
96        EnvironmentConfig { env_vars }
97    }
98
99    /// Create an empty environment configuration (for reset)
100    pub fn empty() -> Self {
101        EnvironmentConfig {
102            env_vars: EnvVarMap::new(),
103        }
104    }
105
106    /// Get environment variables as a Vec of (key, value) tuples
107    /// for use with Command::envs()
108    pub fn as_env_tuples(&self) -> EnvVarTuples {
109        self.env_vars
110            .iter()
111            .map(|(k, v)| (k.clone(), v.clone()))
112            .collect()
113    }
114}
115
116/// Get the path to the configuration storage file
117///
118/// Returns `~/.claude/cc_auto_switch_setting.json`
119///
120/// # Errors
121/// Returns error if home directory cannot be found
122pub fn get_config_storage_path() -> Result<PathBuf> {
123    let home_dir = dirs::home_dir().context("Could not find home directory")?;
124    Ok(home_dir.join(".claude").join("cc_auto_switch_setting.json"))
125}
126
127/// Validate alias name
128///
129/// # Arguments
130/// * `alias_name` - The alias name to validate
131///
132/// # Returns
133/// Ok(()) if valid, Err with message if invalid
134pub fn validate_alias_name(alias_name: &str) -> Result<()> {
135    if alias_name.is_empty() {
136        anyhow::bail!("Alias name cannot be empty");
137    }
138    if alias_name == "cc" {
139        anyhow::bail!("Alias name 'cc' is reserved and cannot be used");
140    }
141    if alias_name.chars().any(|c| c.is_whitespace()) {
142        anyhow::bail!("Alias name cannot contain whitespace");
143    }
144    Ok(())
145}