envx_core/
project_config.rs

1use color_eyre::Result;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::Path;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ProjectConfig {
8    /// Project name
9    pub name: Option<String>,
10
11    /// Project description
12    pub description: Option<String>,
13
14    /// Required environment variables
15    pub required: Vec<RequiredVar>,
16
17    /// Default values for variables
18    pub defaults: HashMap<String, String>,
19
20    /// Files to auto-load (in order)
21    pub auto_load: Vec<String>,
22
23    /// Profile to activate
24    pub profile: Option<String>,
25
26    /// Scripts to run
27    pub scripts: HashMap<String, Script>,
28
29    /// Validation rules
30    pub validation: ValidationRules,
31
32    /// Inherit from parent directories
33    pub inherit: bool,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct RequiredVar {
38    pub name: String,
39    pub description: Option<String>,
40    pub pattern: Option<String>, // Regex pattern
41    pub example: Option<String>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct Script {
46    pub description: Option<String>,
47    pub run: String,
48    pub env: HashMap<String, String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, Default)]
52pub struct ValidationRules {
53    /// Warn about unused variables
54    pub warn_unused: bool,
55
56    /// Error on invalid variable names
57    pub strict_names: bool,
58
59    /// Custom validation patterns
60    pub patterns: HashMap<String, String>,
61}
62
63impl Default for ProjectConfig {
64    fn default() -> Self {
65        Self {
66            name: None,
67            description: None,
68            required: Vec::new(),
69            defaults: HashMap::new(),
70            auto_load: vec![".env".to_string()],
71            profile: None,
72            scripts: HashMap::new(),
73            validation: ValidationRules::default(),
74            inherit: true,
75        }
76    }
77}
78
79impl ProjectConfig {
80    #[must_use]
81    pub fn new(name: Option<String>) -> Self {
82        Self {
83            name,
84            ..Default::default()
85        }
86    }
87
88    pub fn add_required(&mut self, name: String, description: Option<String>) {
89        self.required.push(RequiredVar {
90            name,
91            description,
92            pattern: None,
93            example: None,
94        });
95    }
96
97    /// Saves the project configuration to a YAML file.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if:
102    /// - The configuration cannot be serialized to YAML
103    /// - The file cannot be written to disk (e.g., permission denied, disk full)
104    pub fn save(&self, path: &Path) -> Result<()> {
105        let yaml = serde_yaml::to_string(self)?;
106        std::fs::write(path, yaml)?;
107        Ok(())
108    }
109
110    /// Loads a project configuration from a YAML file.
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if:
115    /// - The file cannot be read (e.g., file not found, permission denied)
116    /// - The file content is not valid UTF-8
117    /// - The YAML content cannot be parsed into a valid `ProjectConfig`
118    pub fn load(path: &Path) -> Result<Self> {
119        let content = std::fs::read_to_string(path)?;
120        let config = serde_yaml::from_str(&content)?;
121        Ok(config)
122    }
123}