1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Serialize, Deserialize, Clone)]
5pub struct ConfigFile {
6 pub project_name: String,
7 pub version: String,
8 pub environments: HashMap<String, EnvironmentConfig>,
9 pub salt: Option<String>,
10}
11
12#[derive(Debug, Serialize, Deserialize, Clone, Default)]
13pub struct EnvironmentConfig {
14 pub parent: Option<String>,
15 pub entries: HashMap<String, ConfigValueEntry>,
16}
17
18#[derive(Debug, Serialize, Deserialize, Clone)]
19pub struct ConfigValueEntry {
20 pub value: String,
21 pub r#type: String,
22 pub is_secret: bool,
23 pub encrypted: bool,
24}
25
26impl ConfigValueEntry {
27 pub fn new(value: &str, r#type: &str, is_secret: bool) -> Self {
28 ConfigValueEntry {
29 value: value.to_string(),
30 r#type: r#type.to_string(),
31 is_secret,
32 encrypted: false,
33 }
34 }
35
36 pub fn validate(
37 &self,
38 field: &crate::core::schema_model::FieldDefinition,
39 ) -> Result<(), String> {
40 crate::core::validator::validate_value(&self.value, field)
41 }
42}
43
44impl ConfigFile {
45 pub fn new(project_name: &str) -> Self {
46 ConfigFile {
47 project_name: project_name.to_string(),
48 version: "1.0.0".to_string(),
49 environments: HashMap::new(),
50 salt: None,
51 }
52 }
53
54 pub fn add_environment(&mut self, name: &str) -> &mut EnvironmentConfig {
55 self.environments
56 .entry(name.to_string())
57 .or_insert_with(|| EnvironmentConfig::default())
58 }
59
60 pub fn get_environment(&self, name: &str) -> Option<&EnvironmentConfig> {
61 self.environments.get(name)
62 }
63
64 pub fn get_environment_mut(&mut self, name: &str) -> Option<&mut EnvironmentConfig> {
65 self.environments.get_mut(name)
66 }
67}
68
69impl EnvironmentConfig {
70 pub fn new() -> Self {
71 EnvironmentConfig {
72 parent: None,
73 entries: HashMap::new(),
74 }
75 }
76
77 pub fn with_parent(parent: &str) -> Self {
78 EnvironmentConfig {
79 parent: Some(parent.to_string()),
80 entries: HashMap::new(),
81 }
82 }
83
84 pub fn set_value(&mut self, key: &str, value: ConfigValueEntry) {
85 self.entries.insert(key.to_string(), value);
86 }
87
88 pub fn get_value(&self, key: &str) -> Option<&ConfigValueEntry> {
89 self.entries.get(key)
90 }
91
92 pub fn remove_value(&mut self, key: &str) -> Option<ConfigValueEntry> {
93 self.entries.remove(key)
94 }
95
96 pub fn keys(&self) -> impl Iterator<Item = &String> {
97 self.entries.keys()
98 }
99
100 pub fn values(&self) -> impl Iterator<Item = &ConfigValueEntry> {
101 self.entries.values()
102 }
103
104 pub fn is_empty(&self) -> bool {
105 self.entries.is_empty()
106 }
107
108 pub fn len(&self) -> usize {
109 self.entries.len()
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_config_value_entry_new() {
119 let entry = ConfigValueEntry::new("test_value", "string", true);
120 assert_eq!(entry.value, "test_value");
121 assert_eq!(entry.r#type, "string");
122 assert!(entry.is_secret);
123 assert!(!entry.encrypted);
124 }
125
126 #[test]
127 fn test_config_file_new() {
128 let config = ConfigFile::new("TestProject");
129 assert_eq!(config.project_name, "TestProject");
130 assert_eq!(config.version, "1.0.0");
131 assert!(config.environments.is_empty());
132 }
133
134 #[test]
135 fn test_environment_config_new() {
136 let env = EnvironmentConfig::new();
137 assert!(env.parent.is_none());
138 assert!(env.entries.is_empty());
139 }
140
141 #[test]
142 fn test_environment_config_with_parent() {
143 let env = EnvironmentConfig::with_parent("development");
144 assert_eq!(env.parent, Some("development".to_string()));
145 }
146
147 #[test]
148 fn test_config_file_add_environment() {
149 let mut config = ConfigFile::new("Test");
150 config.add_environment("production");
151 assert!(config.environments.contains_key("production"));
152 }
153
154 #[test]
155 fn test_config_value_entry_validate_mismatch() {
156 let entry = ConfigValueEntry::new("not_an_int", "integer", false);
157 let field = crate::core::schema_model::FieldDefinition {
158 key: "test".into(),
159 r#type: "integer".into(),
160 description: None,
161 validation: None,
162 is_secret: false,
163 };
164 assert!(entry.validate(&field).is_err());
165 }
166
167 #[test]
168 fn test_config_file_clone() {
169 let mut environments = HashMap::new();
170 let mut entries = HashMap::new();
171 entries.insert("K1".into(), ConfigValueEntry::new("V1", "string", false));
172 environments.insert(
173 "dev".into(),
174 EnvironmentConfig {
175 parent: None,
176 entries,
177 },
178 );
179
180 let config = ConfigFile {
181 project_name: "Test".into(),
182 version: "1.0".into(),
183 environments,
184 salt: None,
185 };
186
187 let cloned = config.clone();
188 assert_eq!(cloned.project_name, config.project_name);
189 assert_eq!(cloned.environments.len(), config.environments.len());
190 }
191
192 #[test]
193 fn test_empty_config_value_entry() {
194 let entry = ConfigValueEntry::new("", "string", false);
195 assert_eq!(entry.value, "");
196 }
197
198 #[test]
199 fn test_environment_set_and_get_value() {
200 let mut env = EnvironmentConfig::new();
201 env.set_value("KEY1", ConfigValueEntry::new("value1", "string", false));
202
203 assert!(env.get_value("KEY1").is_some());
204 assert_eq!(env.get_value("KEY1").unwrap().value, "value1");
205 }
206
207 #[test]
208 fn test_environment_remove_value() {
209 let mut env = EnvironmentConfig::new();
210 env.set_value("KEY1", ConfigValueEntry::new("value1", "string", false));
211
212 let removed = env.remove_value("KEY1");
213 assert!(removed.is_some());
214 assert!(env.get_value("KEY1").is_none());
215 }
216
217 #[test]
218 fn test_environment_len_and_is_empty() {
219 let mut env = EnvironmentConfig::new();
220 assert!(env.is_empty());
221 assert_eq!(env.len(), 0);
222
223 env.set_value("KEY1", ConfigValueEntry::new("value1", "string", false));
224 assert!(!env.is_empty());
225 assert_eq!(env.len(), 1);
226 }
227}