greentic_dev/
config.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::PathBuf;
4
5use anyhow::{Context, Result};
6use serde::Deserialize;
7
8#[derive(Debug, Default, Deserialize)]
9pub struct GreenticConfig {
10    #[serde(default)]
11    pub tools: ToolsSection,
12    #[serde(default)]
13    pub defaults: DefaultsSection,
14    #[serde(default)]
15    pub distributor: DistributorSection,
16}
17
18#[derive(Debug, Default, Deserialize)]
19pub struct ToolsSection {
20    #[serde(rename = "greentic-component", default)]
21    pub greentic_component: ToolEntry,
22    #[serde(rename = "packc", default)]
23    pub packc: ToolEntry,
24    #[serde(rename = "packc-path", default)]
25    pub packc_path: ToolEntry,
26}
27
28#[derive(Debug, Default, Deserialize)]
29pub struct ToolEntry {
30    pub path: Option<PathBuf>,
31}
32
33#[allow(dead_code)]
34#[derive(Debug, Default, Deserialize)]
35pub struct DefaultsSection {
36    #[serde(default)]
37    pub component: ComponentDefaults,
38}
39
40#[allow(dead_code)]
41#[derive(Debug, Default, Deserialize)]
42pub struct ComponentDefaults {
43    pub org: Option<String>,
44    pub template: Option<String>,
45}
46
47#[derive(Debug, Default, Deserialize)]
48pub struct DistributorSection {
49    /// Map of profile name -> profile configuration.
50    #[serde(default, flatten)]
51    pub profiles: HashMap<String, DistributorProfileConfig>,
52}
53
54#[derive(Debug, Clone, Deserialize)]
55pub struct DistributorProfileConfig {
56    /// Base URL for the distributor (preferred field; falls back to `url` if set).
57    #[serde(default)]
58    pub base_url: Option<String>,
59    /// Deprecated alias for base_url.
60    #[serde(default)]
61    pub url: Option<String>,
62    /// API token; allow env:VAR indirection.
63    #[serde(default)]
64    pub token: Option<String>,
65    /// Tenant identifier for distributor requests.
66    #[serde(default)]
67    pub tenant_id: Option<String>,
68    /// Environment identifier for distributor requests.
69    #[serde(default)]
70    pub environment_id: Option<String>,
71    /// Additional headers (optional).
72    #[serde(default)]
73    pub headers: Option<HashMap<String, String>>,
74}
75
76pub fn load() -> Result<GreenticConfig> {
77    let path_override = std::env::var("GREENTIC_CONFIG").ok();
78    load_from(path_override.as_deref())
79}
80
81pub fn load_from(path_override: Option<&str>) -> Result<GreenticConfig> {
82    let Some(path) = config_path_override(path_override) else {
83        return Ok(GreenticConfig::default());
84    };
85
86    if !path.exists() {
87        return Ok(GreenticConfig::default());
88    }
89
90    let raw = fs::read_to_string(&path)
91        .with_context(|| format!("failed to read config at {}", path.display()))?;
92    let config: GreenticConfig = toml::from_str(&raw)
93        .with_context(|| format!("failed to parse config at {}", path.display()))?;
94    Ok(config)
95}
96
97fn config_path_override(path_override: Option<&str>) -> Option<PathBuf> {
98    if let Some(raw) = path_override {
99        return Some(PathBuf::from(raw));
100    }
101    config_path()
102}
103
104pub fn config_path() -> Option<PathBuf> {
105    // Prefer XDG-style config path, but fall back to legacy ~/.greentic/config.toml.
106    if let Some(mut dir) = dirs::config_dir() {
107        dir.push("greentic-dev");
108        dir.push("config.toml");
109        if dir.exists() {
110            return Some(dir);
111        }
112    }
113    dirs::home_dir().map(|mut home| {
114        home.push(".greentic");
115        home.push("config.toml");
116        home
117    })
118}