llm_config_storage/
models.rs

1//! Core storage models
2
3use chrono::{DateTime, Utc};
4use llm_config_crypto::EncryptedData;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use uuid::Uuid;
8
9/// Environment type for configuration
10#[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/// Configuration value that can be a simple type or a secret
48#[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    /// Check if this value is a secret
62    pub fn is_secret(&self) -> bool {
63        matches!(self, ConfigValue::Secret(_))
64    }
65
66    /// Get as string if possible
67    pub fn as_str(&self) -> Option<&str> {
68        match self {
69            ConfigValue::String(s) => Some(s),
70            _ => None,
71        }
72    }
73
74    /// Get as integer if possible
75    pub fn as_i64(&self) -> Option<i64> {
76        match self {
77            ConfigValue::Integer(i) => Some(*i),
78            _ => None,
79        }
80    }
81
82    /// Get as float if possible
83    pub fn as_f64(&self) -> Option<f64> {
84        match self {
85            ConfigValue::Float(f) => Some(*f),
86            _ => None,
87        }
88    }
89
90    /// Get as boolean if possible
91    pub fn as_bool(&self) -> Option<bool> {
92        match self {
93            ConfigValue::Boolean(b) => Some(*b),
94            _ => None,
95        }
96    }
97}
98
99/// Configuration metadata
100#[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/// A configuration entry
127#[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    /// Get the full path (namespace + key)
157    pub fn full_path(&self) -> String {
158        format!("{}/{}", self.namespace, self.key)
159    }
160}
161
162/// Version history entry
163#[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}