1use crate::types::{Document, Value};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Schema {
19 pub name: String,
20 pub fields: HashMap<String, FieldSchema>,
21 pub required: Vec<String>,
22 pub additional_properties: bool,
23}
24
25impl Schema {
26 pub fn new(name: impl Into<String>) -> Self {
28 Self {
29 name: name.into(),
30 fields: HashMap::new(),
31 required: Vec::new(),
32 additional_properties: true,
33 }
34 }
35
36 pub fn field(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
38 self.fields.insert(name.into(), schema);
39 self
40 }
41
42 pub fn require(mut self, name: impl Into<String>) -> Self {
44 self.required.push(name.into());
45 self
46 }
47
48 pub fn additional_properties(mut self, allow: bool) -> Self {
50 self.additional_properties = allow;
51 self
52 }
53
54 pub fn validate(&self, doc: &Document) -> ValidationResult {
56 let mut errors = Vec::new();
57
58 for required in &self.required {
59 if !doc.contains(required) {
60 errors.push(format!("Missing required field: {}", required));
61 }
62 }
63
64 for (field_name, field_schema) in &self.fields {
65 if let Some(value) = doc.get(field_name) {
66 if let Err(err) = field_schema.validate(value) {
67 errors.push(format!("Field '{}': {}", field_name, err));
68 }
69 }
70 }
71
72 if !self.additional_properties {
73 for key in doc.keys() {
74 if !self.fields.contains_key(key) {
75 errors.push(format!("Unknown field: {}", key));
76 }
77 }
78 }
79
80 ValidationResult {
81 is_valid: errors.is_empty(),
82 errors,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct FieldSchema {
94 pub field_type: FieldType,
95 pub nullable: bool,
96 pub min: Option<f64>,
97 pub max: Option<f64>,
98 pub min_length: Option<usize>,
99 pub max_length: Option<usize>,
100 pub pattern: Option<String>,
101 pub enum_values: Option<Vec<Value>>,
102 pub items: Option<Box<FieldSchema>>,
103 pub properties: Option<HashMap<String, FieldSchema>>,
104}
105
106impl FieldSchema {
107 pub fn new(field_type: FieldType) -> Self {
108 Self {
109 field_type,
110 nullable: false,
111 min: None,
112 max: None,
113 min_length: None,
114 max_length: None,
115 pattern: None,
116 enum_values: None,
117 items: None,
118 properties: None,
119 }
120 }
121
122 pub fn string() -> Self {
123 Self::new(FieldType::String)
124 }
125
126 pub fn int() -> Self {
127 Self::new(FieldType::Int)
128 }
129
130 pub fn float() -> Self {
131 Self::new(FieldType::Float)
132 }
133
134 pub fn bool() -> Self {
135 Self::new(FieldType::Bool)
136 }
137
138 pub fn array(items: FieldSchema) -> Self {
139 let mut schema = Self::new(FieldType::Array);
140 schema.items = Some(Box::new(items));
141 schema
142 }
143
144 pub fn object() -> Self {
145 Self::new(FieldType::Object)
146 }
147
148 pub fn nullable(mut self) -> Self {
149 self.nullable = true;
150 self
151 }
152
153 pub fn min(mut self, min: f64) -> Self {
154 self.min = Some(min);
155 self
156 }
157
158 pub fn max(mut self, max: f64) -> Self {
159 self.max = Some(max);
160 self
161 }
162
163 pub fn min_length(mut self, len: usize) -> Self {
164 self.min_length = Some(len);
165 self
166 }
167
168 pub fn max_length(mut self, len: usize) -> Self {
169 self.max_length = Some(len);
170 self
171 }
172
173 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
174 self.pattern = Some(pattern.into());
175 self
176 }
177
178 pub fn enum_values(mut self, values: Vec<Value>) -> Self {
179 self.enum_values = Some(values);
180 self
181 }
182
183 pub fn validate(&self, value: &Value) -> Result<(), String> {
185 if value.is_null() {
186 if self.nullable {
187 return Ok(());
188 }
189 return Err("Value cannot be null".to_string());
190 }
191
192 if !self.field_type.matches(value) {
193 return Err(format!(
194 "Expected type {:?}, got {:?}",
195 self.field_type,
196 value_type(value)
197 ));
198 }
199
200 if let Some(ref enum_values) = self.enum_values {
201 if !enum_values.contains(value) {
202 return Err("Value not in allowed enum values".to_string());
203 }
204 }
205
206 match value {
207 Value::Int(n) => {
208 if let Some(min) = self.min {
209 if (*n as f64) < min {
210 return Err(format!("Value {} is less than minimum {}", n, min));
211 }
212 }
213 if let Some(max) = self.max {
214 if (*n as f64) > max {
215 return Err(format!("Value {} is greater than maximum {}", n, max));
216 }
217 }
218 }
219 Value::Float(f) => {
220 if let Some(min) = self.min {
221 if *f < min {
222 return Err(format!("Value {} is less than minimum {}", f, min));
223 }
224 }
225 if let Some(max) = self.max {
226 if *f > max {
227 return Err(format!("Value {} is greater than maximum {}", f, max));
228 }
229 }
230 }
231 Value::String(s) => {
232 if let Some(min_len) = self.min_length {
233 if s.len() < min_len {
234 return Err(format!(
235 "String length {} is less than minimum {}",
236 s.len(),
237 min_len
238 ));
239 }
240 }
241 if let Some(max_len) = self.max_length {
242 if s.len() > max_len {
243 return Err(format!(
244 "String length {} is greater than maximum {}",
245 s.len(),
246 max_len
247 ));
248 }
249 }
250 if let Some(ref pattern) = self.pattern {
251 let re = regex::Regex::new(pattern)
252 .map_err(|_| "Invalid regex pattern".to_string())?;
253 if !re.is_match(s) {
254 return Err(format!("String does not match pattern: {}", pattern));
255 }
256 }
257 }
258 Value::Array(arr) => {
259 if let Some(min_len) = self.min_length {
260 if arr.len() < min_len {
261 return Err(format!(
262 "Array length {} is less than minimum {}",
263 arr.len(),
264 min_len
265 ));
266 }
267 }
268 if let Some(max_len) = self.max_length {
269 if arr.len() > max_len {
270 return Err(format!(
271 "Array length {} is greater than maximum {}",
272 arr.len(),
273 max_len
274 ));
275 }
276 }
277 if let Some(ref items_schema) = self.items {
278 for (i, item) in arr.iter().enumerate() {
279 if let Err(e) = items_schema.validate(item) {
280 return Err(format!("Array item {}: {}", i, e));
281 }
282 }
283 }
284 }
285 Value::Object(obj) => {
286 if let Some(ref props) = self.properties {
287 for (key, prop_schema) in props {
288 if let Some(value) = obj.get(key) {
289 if let Err(e) = prop_schema.validate(value) {
290 return Err(format!("Property '{}': {}", key, e));
291 }
292 }
293 }
294 }
295 }
296 _ => {}
297 }
298
299 Ok(())
300 }
301}
302
303fn value_type(value: &Value) -> &'static str {
304 match value {
305 Value::Null => "null",
306 Value::Bool(_) => "bool",
307 Value::Int(_) => "int",
308 Value::Float(_) => "float",
309 Value::String(_) => "string",
310 Value::Array(_) => "array",
311 Value::Object(_) => "object",
312 }
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
321pub enum FieldType {
322 String,
323 Int,
324 Float,
325 Number,
326 Bool,
327 Array,
328 Object,
329 Any,
330}
331
332impl FieldType {
333 fn matches(&self, value: &Value) -> bool {
334 match (self, value) {
335 (Self::Any, _) => true,
336 (Self::String, Value::String(_)) => true,
337 (Self::Int, Value::Int(_)) => true,
338 (Self::Float, Value::Float(_)) => true,
339 (Self::Number, Value::Int(_) | Value::Float(_)) => true,
340 (Self::Bool, Value::Bool(_)) => true,
341 (Self::Array, Value::Array(_)) => true,
342 (Self::Object, Value::Object(_)) => true,
343 _ => false,
344 }
345 }
346}
347
348#[derive(Debug, Clone)]
354pub struct ValidationResult {
355 pub is_valid: bool,
356 pub errors: Vec<String>,
357}
358
359impl ValidationResult {
360 pub fn valid() -> Self {
361 Self {
362 is_valid: true,
363 errors: Vec::new(),
364 }
365 }
366
367 pub fn invalid(errors: Vec<String>) -> Self {
368 Self {
369 is_valid: false,
370 errors,
371 }
372 }
373}
374
375pub struct SchemaBuilder {
381 schema: Schema,
382}
383
384impl SchemaBuilder {
385 pub fn new(name: impl Into<String>) -> Self {
386 Self {
387 schema: Schema::new(name),
388 }
389 }
390
391 pub fn field(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
392 self.schema.fields.insert(name.into(), schema);
393 self
394 }
395
396 pub fn required_field(mut self, name: impl Into<String>, schema: FieldSchema) -> Self {
397 let name = name.into();
398 self.schema.fields.insert(name.clone(), schema);
399 self.schema.required.push(name);
400 self
401 }
402
403 pub fn additional_properties(mut self, allow: bool) -> Self {
404 self.schema.additional_properties = allow;
405 self
406 }
407
408 pub fn build(self) -> Schema {
409 self.schema
410 }
411}
412
413#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_type_validation() {
423 let schema = FieldSchema::string();
424 assert!(schema.validate(&Value::String("hello".to_string())).is_ok());
425 assert!(schema.validate(&Value::Int(42)).is_err());
426
427 let schema = FieldSchema::int();
428 assert!(schema.validate(&Value::Int(42)).is_ok());
429 assert!(schema.validate(&Value::String("42".to_string())).is_err());
430 }
431
432 #[test]
433 fn test_nullable() {
434 let schema = FieldSchema::string();
435 assert!(schema.validate(&Value::Null).is_err());
436
437 let schema = FieldSchema::string().nullable();
438 assert!(schema.validate(&Value::Null).is_ok());
439 }
440
441 #[test]
442 fn test_range_validation() {
443 let schema = FieldSchema::int().min(0.0).max(100.0);
444
445 assert!(schema.validate(&Value::Int(50)).is_ok());
446 assert!(schema.validate(&Value::Int(-1)).is_err());
447 assert!(schema.validate(&Value::Int(101)).is_err());
448 }
449
450 #[test]
451 fn test_string_length() {
452 let schema = FieldSchema::string().min_length(3).max_length(10);
453
454 assert!(schema.validate(&Value::String("hello".to_string())).is_ok());
455 assert!(schema.validate(&Value::String("hi".to_string())).is_err());
456 assert!(schema
457 .validate(&Value::String("hello world!".to_string()))
458 .is_err());
459 }
460
461 #[test]
462 fn test_pattern_validation() {
463 let schema = FieldSchema::string().pattern(r"^\d{3}-\d{4}$");
464
465 assert!(schema.validate(&Value::String("123-4567".to_string())).is_ok());
466 assert!(schema.validate(&Value::String("invalid".to_string())).is_err());
467 }
468
469 #[test]
470 fn test_schema_validation() {
471 let schema = SchemaBuilder::new("User")
472 .required_field("name", FieldSchema::string().min_length(1))
473 .required_field("age", FieldSchema::int().min(0.0))
474 .field("email", FieldSchema::string().nullable())
475 .build();
476
477 let mut doc = Document::new();
478 doc.set("name", "Alice");
479 doc.set("age", 30i64);
480
481 let result = schema.validate(&doc);
482 assert!(result.is_valid);
483
484 let mut invalid_doc = Document::new();
485 invalid_doc.set("name", "Bob");
486
487 let result = schema.validate(&invalid_doc);
488 assert!(!result.is_valid);
489 assert!(result.errors.iter().any(|e| e.contains("age")));
490 }
491
492 #[test]
493 fn test_enum_validation() {
494 let schema = FieldSchema::string().enum_values(vec![
495 Value::String("active".to_string()),
496 Value::String("inactive".to_string()),
497 ]);
498
499 assert!(schema.validate(&Value::String("active".to_string())).is_ok());
500 assert!(schema.validate(&Value::String("unknown".to_string())).is_err());
501 }
502
503 #[test]
504 fn test_array_validation() {
505 let schema = FieldSchema::array(FieldSchema::int()).min_length(1).max_length(5);
506
507 assert!(schema.validate(&Value::Array(vec![Value::Int(1), Value::Int(2)])).is_ok());
508 assert!(schema.validate(&Value::Array(vec![])).is_err());
509 assert!(schema
510 .validate(&Value::Array(vec![Value::String("not an int".to_string())]))
511 .is_err());
512 }
513}