Skip to main content

naru_core/core/
schema_model.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Serialize, Deserialize, Clone)]
4pub struct SchemaFile {
5    pub version: String,
6    pub fields: Vec<FieldDefinition>,
7}
8
9#[derive(Debug, Serialize, Deserialize, Clone)]
10pub struct FieldDefinition {
11    pub key: String,
12    pub r#type: String,
13    pub description: Option<String>,
14    pub validation: Option<ValidationRules>,
15    pub is_secret: bool,
16}
17
18#[derive(Debug, Serialize, Deserialize, Clone)]
19pub struct ValidationRules {
20    pub min_length: Option<usize>,
21    pub max_length: Option<usize>,
22    pub min_value: Option<i64>,
23    pub max_value: Option<i64>,
24    pub pattern: Option<String>,
25}
26
27impl SchemaFile {
28    pub fn new(version: &str) -> Self {
29        SchemaFile {
30            version: version.to_string(),
31            fields: Vec::new(),
32        }
33    }
34
35    pub fn add_field(&mut self, field: FieldDefinition) {
36        self.fields.push(field);
37    }
38
39    pub fn get_field(&self, key: &str) -> Option<&FieldDefinition> {
40        self.fields.iter().find(|f| f.key == key)
41    }
42
43    pub fn get_field_mut(&mut self, key: &str) -> Option<&mut FieldDefinition> {
44        self.fields.iter_mut().find(|f| f.key == key)
45    }
46
47    pub fn remove_field(&mut self, key: &str) -> Option<FieldDefinition> {
48        if let Some(pos) = self.fields.iter().position(|f| f.key == key) {
49            Some(self.fields.remove(pos))
50        } else {
51            None
52        }
53    }
54
55    pub fn is_empty(&self) -> bool {
56        self.fields.is_empty()
57    }
58
59    pub fn len(&self) -> usize {
60        self.fields.len()
61    }
62
63    pub fn iter(&self) -> impl Iterator<Item = &FieldDefinition> {
64        self.fields.iter()
65    }
66}
67
68impl FieldDefinition {
69    pub fn new(key: &str, r#type: &str) -> Self {
70        FieldDefinition {
71            key: key.to_string(),
72            r#type: r#type.to_string(),
73            description: None,
74            validation: None,
75            is_secret: false,
76        }
77    }
78
79    pub fn with_description(mut self, description: &str) -> Self {
80        self.description = Some(description.to_string());
81        self
82    }
83
84    pub fn with_validation(mut self, validation: ValidationRules) -> Self {
85        self.validation = Some(validation);
86        self
87    }
88
89    pub fn secret(mut self) -> Self {
90        self.is_secret = true;
91        self
92    }
93
94    pub fn is_string(&self) -> bool {
95        self.r#type == "string"
96    }
97
98    pub fn is_integer(&self) -> bool {
99        self.r#type == "integer"
100    }
101
102    pub fn is_boolean(&self) -> bool {
103        self.r#type == "boolean"
104    }
105}
106
107impl ValidationRules {
108    pub fn new() -> Self {
109        ValidationRules {
110            min_length: None,
111            max_length: None,
112            min_value: None,
113            max_value: None,
114            pattern: None,
115        }
116    }
117
118    pub fn with_min_length(mut self, min: usize) -> Self {
119        self.min_length = Some(min);
120        self
121    }
122
123    pub fn with_max_length(mut self, max: usize) -> Self {
124        self.max_length = Some(max);
125        self
126    }
127
128    pub fn with_length_range(mut self, min: usize, max: usize) -> Self {
129        self.min_length = Some(min);
130        self.max_length = Some(max);
131        self
132    }
133
134    pub fn with_min_value(mut self, min: i64) -> Self {
135        self.min_value = Some(min);
136        self
137    }
138
139    pub fn with_max_value(mut self, max: i64) -> Self {
140        self.max_value = Some(max);
141        self
142    }
143
144    pub fn with_value_range(mut self, min: i64, max: i64) -> Self {
145        self.min_value = Some(min);
146        self.max_value = Some(max);
147        self
148    }
149
150    pub fn with_pattern(mut self, pattern: &str) -> Self {
151        self.pattern = Some(pattern.to_string());
152        self
153    }
154}
155
156impl Default for ValidationRules {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_schema_file_new() {
168        let schema = SchemaFile::new("1.0.0");
169        assert_eq!(schema.version, "1.0.0");
170        assert!(schema.fields.is_empty());
171    }
172
173    #[test]
174    fn test_schema_file_add_field() {
175        let mut schema = SchemaFile::new("1.0.0");
176        let field = FieldDefinition::new("username", "string");
177        schema.add_field(field);
178        assert_eq!(schema.len(), 1);
179    }
180
181    #[test]
182    fn test_schema_file_get_field() {
183        let mut schema = SchemaFile::new("1.0.0");
184        let field = FieldDefinition::new("username", "string");
185        schema.add_field(field);
186
187        assert!(schema.get_field("username").is_some());
188        assert!(schema.get_field("nonexistent").is_none());
189    }
190
191    #[test]
192    fn test_schema_file_remove_field() {
193        let mut schema = SchemaFile::new("1.0.0");
194        let field = FieldDefinition::new("username", "string");
195        schema.add_field(field);
196
197        let removed = schema.remove_field("username");
198        assert!(removed.is_some());
199        assert!(schema.get_field("username").is_none());
200    }
201
202    #[test]
203    fn test_field_definition_new() {
204        let field = FieldDefinition::new("db_host", "string");
205        assert_eq!(field.key, "db_host");
206        assert_eq!(field.r#type, "string");
207        assert!(!field.is_secret);
208    }
209
210    #[test]
211    fn test_field_definition_builder() {
212        let field = FieldDefinition::new("email", "string")
213            .with_description("User email address")
214            .with_validation(ValidationRules::new().with_pattern(r"^[\w\.-]+@[\w\.-]+\.\w+$"))
215            .secret();
216
217        assert_eq!(field.key, "email");
218        assert!(field.description.is_some());
219        assert!(field.validation.is_some());
220        assert!(field.is_secret);
221    }
222
223    #[test]
224    fn test_field_type_checkers() {
225        let string_field = FieldDefinition::new("name", "string");
226        let int_field = FieldDefinition::new("age", "integer");
227        let bool_field = FieldDefinition::new("active", "boolean");
228
229        assert!(string_field.is_string());
230        assert!(!string_field.is_integer());
231        assert!(!string_field.is_boolean());
232
233        assert!(int_field.is_integer());
234        assert!(bool_field.is_boolean());
235    }
236
237    #[test]
238    fn test_validation_rules_builder() {
239        let rules = ValidationRules::new()
240            .with_min_length(3)
241            .with_max_length(10)
242            .with_min_value(0)
243            .with_max_value(120)
244            .with_pattern(r"^[a-z]+$");
245
246        assert_eq!(rules.min_length, Some(3));
247        assert_eq!(rules.max_length, Some(10));
248        assert_eq!(rules.min_value, Some(0));
249        assert_eq!(rules.max_value, Some(120));
250        assert!(rules.pattern.is_some());
251    }
252
253    #[test]
254    fn test_validation_rules_length_range() {
255        let rules = ValidationRules::new().with_length_range(5, 15);
256        assert_eq!(rules.min_length, Some(5));
257        assert_eq!(rules.max_length, Some(15));
258    }
259
260    #[test]
261    fn test_validation_rules_value_range() {
262        let rules = ValidationRules::new().with_value_range(-100, 100);
263        assert_eq!(rules.min_value, Some(-100));
264        assert_eq!(rules.max_value, Some(100));
265    }
266
267    #[test]
268    fn test_schema_iter() {
269        let mut schema = SchemaFile::new("1.0.0");
270        schema.add_field(FieldDefinition::new("field1", "string"));
271        schema.add_field(FieldDefinition::new("field2", "integer"));
272
273        let keys: Vec<_> = schema.iter().map(|f| f.key.clone()).collect();
274        assert_eq!(keys, vec!["field1", "field2"]);
275    }
276}