athena_cli/
config.rs

1use anyhow::Result;
2use directories::ProjectDirs;
3use serde::{Deserialize, Serialize};
4use std::{path::PathBuf, time::Duration};
5
6#[derive(Debug, Serialize, Deserialize)]
7pub struct Config {
8    pub aws: AwsConfig,
9    pub app: AppConfig,
10}
11
12#[derive(Debug, Serialize, Deserialize)]
13pub struct AwsConfig {
14    pub region: Option<String>,
15    pub workgroup: Option<String>,
16    pub output_location: String,
17    pub catalog: Option<String>,
18    pub database: Option<String>,
19    pub profile: Option<String>,
20}
21
22#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
23pub enum HistorySortBy {
24    StartTime,
25    EndTime,
26    DataScanned,
27    Status,
28}
29
30impl Default for HistorySortBy {
31    fn default() -> Self {
32        Self::StartTime
33    }
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct AppConfig {
38    #[serde(with = "humantime_serde")]
39    pub query_reuse_time: Duration,
40    pub max_rows: usize,
41    /// Default number of history items to show
42    #[serde(default = "default_history_size")]
43    pub history_size: i32,
44    /// Fields to display in history view
45    #[serde(default)]
46    pub history_fields: Option<Vec<String>>,
47    /// Fields to display in inspect view
48    #[serde(default)]
49    pub inspect_fields: Option<Vec<String>>,
50}
51
52fn default_history_size() -> i32 {
53    20
54}
55
56impl Default for Config {
57    fn default() -> Self {
58        Self {
59            aws: AwsConfig {
60                region: Some("eu-west-1".to_string()),
61                workgroup: Some("primary".to_string()),
62                output_location: "s3://athena-query-results/".to_string(),
63                catalog: Some("AwsDataCatalog".to_string()),
64                database: None,
65                profile: None,
66            },
67            app: AppConfig {
68                query_reuse_time: Duration::from_secs(3600), // 1 hour
69                max_rows: 1000,
70                history_size: 20,
71                history_fields: None,
72                inspect_fields: None,
73            },
74        }
75    }
76}
77
78impl Config {
79    pub fn load() -> Result<Self> {
80        let config_path = get_config_path()?;
81
82        println!("Looking for config at: {}", config_path.display());
83
84        if !config_path.exists() {
85            println!("Config file not found, creating default");
86            let config = Config::default();
87            std::fs::create_dir_all(config_path.parent().unwrap())?;
88            std::fs::write(&config_path, toml::to_string_pretty(&config)?)?;
89            return Ok(config);
90        }
91
92        println!("Loading config from: {}", config_path.display());
93        let config = config::Config::builder()
94            .add_source(config::File::from(config_path))
95            .build()?;
96
97        let config: Config = config.try_deserialize()?;
98        println!("Loaded workgroup: {:?}", config.aws.workgroup);
99
100        Ok(config)
101    }
102}
103
104fn get_config_path() -> Result<PathBuf> {
105    // Always use XDG config dir (~/.config/athena-cli/config.toml)
106    if let Ok(home) = std::env::var("HOME") {
107        return Ok(PathBuf::from(home).join(".config/athena-cli/config.toml"));
108    }
109
110    // Fallback only if HOME is not available
111    let proj_dirs = ProjectDirs::from("com", "your-org", "athena-cli")
112        .ok_or_else(|| anyhow::anyhow!("Could not determine config directory"))?;
113
114    Ok(proj_dirs.config_dir().join("config.toml"))
115}