1use chrono::{DateTime, Utc};
4use llm_config_crypto::EncryptedData;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Environment {
13 Base,
14 Development,
15 Staging,
16 Production,
17 Edge,
18}
19
20impl std::str::FromStr for Environment {
21 type Err = String;
22
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 match s.to_lowercase().as_str() {
25 "base" => Ok(Environment::Base),
26 "dev" | "development" => Ok(Environment::Development),
27 "staging" | "stage" => Ok(Environment::Staging),
28 "prod" | "production" => Ok(Environment::Production),
29 "edge" => Ok(Environment::Edge),
30 _ => Err(format!("Unknown environment: {}", s)),
31 }
32 }
33}
34
35impl std::fmt::Display for Environment {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Environment::Base => write!(f, "base"),
39 Environment::Development => write!(f, "development"),
40 Environment::Staging => write!(f, "staging"),
41 Environment::Production => write!(f, "production"),
42 Environment::Edge => write!(f, "edge"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum ConfigValue {
51 String(String),
52 Integer(i64),
53 Float(f64),
54 Boolean(bool),
55 Array(Vec<ConfigValue>),
56 Object(HashMap<String, ConfigValue>),
57 Secret(EncryptedData),
58}
59
60impl ConfigValue {
61 pub fn is_secret(&self) -> bool {
63 matches!(self, ConfigValue::Secret(_))
64 }
65
66 pub fn as_str(&self) -> Option<&str> {
68 match self {
69 ConfigValue::String(s) => Some(s),
70 _ => None,
71 }
72 }
73
74 pub fn as_i64(&self) -> Option<i64> {
76 match self {
77 ConfigValue::Integer(i) => Some(*i),
78 _ => None,
79 }
80 }
81
82 pub fn as_f64(&self) -> Option<f64> {
84 match self {
85 ConfigValue::Float(f) => Some(*f),
86 _ => None,
87 }
88 }
89
90 pub fn as_bool(&self) -> Option<bool> {
92 match self {
93 ConfigValue::Boolean(b) => Some(*b),
94 _ => None,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ConfigMetadata {
102 pub created_at: DateTime<Utc>,
103 pub created_by: String,
104 pub updated_at: DateTime<Utc>,
105 pub updated_by: String,
106 #[serde(default)]
107 pub tags: Vec<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub description: Option<String>,
110}
111
112impl Default for ConfigMetadata {
113 fn default() -> Self {
114 let now = Utc::now();
115 Self {
116 created_at: now,
117 created_by: "system".to_string(),
118 updated_at: now,
119 updated_by: "system".to_string(),
120 tags: Vec::new(),
121 description: None,
122 }
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ConfigEntry {
129 pub id: Uuid,
130 pub namespace: String,
131 pub key: String,
132 pub value: ConfigValue,
133 pub environment: Environment,
134 pub version: u64,
135 pub metadata: ConfigMetadata,
136}
137
138impl ConfigEntry {
139 pub fn new(
140 namespace: impl Into<String>,
141 key: impl Into<String>,
142 value: ConfigValue,
143 environment: Environment,
144 ) -> Self {
145 Self {
146 id: Uuid::new_v4(),
147 namespace: namespace.into(),
148 key: key.into(),
149 value,
150 environment,
151 version: 1,
152 metadata: ConfigMetadata::default(),
153 }
154 }
155
156 pub fn full_path(&self) -> String {
158 format!("{}/{}", self.namespace, self.key)
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct VersionEntry {
165 pub version: u64,
166 pub config_id: Uuid,
167 pub namespace: String,
168 pub key: String,
169 pub value: ConfigValue,
170 pub environment: Environment,
171 pub created_at: DateTime<Utc>,
172 pub created_by: String,
173 pub change_description: Option<String>,
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_environment_parsing() {
182 assert_eq!("dev".parse::<Environment>().unwrap(), Environment::Development);
183 assert_eq!("production".parse::<Environment>().unwrap(), Environment::Production);
184 assert!("invalid".parse::<Environment>().is_err());
185 }
186
187 #[test]
188 fn test_config_value_types() {
189 let string_val = ConfigValue::String("test".to_string());
190 assert_eq!(string_val.as_str(), Some("test"));
191
192 let int_val = ConfigValue::Integer(42);
193 assert_eq!(int_val.as_i64(), Some(42));
194
195 let bool_val = ConfigValue::Boolean(true);
196 assert_eq!(bool_val.as_bool(), Some(true));
197 }
198
199 #[test]
200 fn test_config_entry_creation() {
201 let entry = ConfigEntry::new(
202 "test/namespace",
203 "config.key",
204 ConfigValue::String("value".to_string()),
205 Environment::Development,
206 );
207
208 assert_eq!(entry.namespace, "test/namespace");
209 assert_eq!(entry.key, "config.key");
210 assert_eq!(entry.version, 1);
211 assert_eq!(entry.full_path(), "test/namespace/config.key");
212 }
213}