teaql_core/
dynamic_properties.rs1use crate::{DataType, Value};
3use std::{collections::BTreeMap, sync::Arc};
4
5#[derive(Debug, PartialEq, Eq)]
6pub struct DynamicPropertyDefinitions {
7 types: BTreeMap<String, DataType>,
8 hash: u64,
9}
10
11impl DynamicPropertyDefinitions {
12 pub fn new(fields: impl IntoIterator<Item = (String, DataType)>) -> Result<Arc<Self>, String> {
13 let mut types = BTreeMap::new();
14 for (name, data_type) in fields {
15 if !name.starts_with('_') || name[1..].trim().is_empty() {
16 return Err("dynamic property metadata requires '_' names".into());
17 }
18 if types.insert(name.clone(), data_type).is_some() {
19 return Err(format!("duplicate dynamic property definition: {name}"));
20 }
21 }
22 Ok(Self::from_types(types))
23 }
24 fn from_types(types: BTreeMap<String, DataType>) -> Arc<Self> {
25 use std::hash::{Hash, Hasher};
26 let mut hash = std::collections::hash_map::DefaultHasher::new();
27 for (name, data_type) in &types {
28 name.hash(&mut hash);
29 std::mem::discriminant(data_type).hash(&mut hash);
30 }
31 Arc::new(Self {
32 types,
33 hash: hash.finish(),
34 })
35 }
36 pub fn types(&self) -> &BTreeMap<String, DataType> {
37 &self.types
38 }
39 pub fn data_type(&self, name: &str) -> Option<DataType> {
40 self.types.get(name).copied()
41 }
42
43 pub fn validate_value(&self, name: &str, value: &Value) -> Result<(), String> {
44 let Some(declared) = self.data_type(name) else {
45 return Ok(());
46 };
47 let matches = match value {
48 Value::Null => true,
49 Value::TypedNull(actual) => *actual == declared,
50 Value::Bool(_) => declared == DataType::Bool,
51 Value::I64(_) => declared == DataType::I64,
52 Value::U64(_) => declared == DataType::U64,
53 Value::F64(_) => declared == DataType::F64,
54 Value::Decimal(_) => declared == DataType::Decimal,
55 Value::Text(_) => matches!(declared, DataType::Text | DataType::LargeText),
56 Value::Date(_) => declared == DataType::Date,
57 Value::Timestamp(_) => declared == DataType::Timestamp,
58 Value::Json(json) => {
59 json.is_null()
60 || match declared {
61 DataType::Json => true,
62 DataType::Bool => json.is_boolean(),
63 DataType::I64 => json.as_i64().is_some(),
64 DataType::U64 => json.as_u64().is_some(),
65 DataType::F64 => json.is_number(),
66 DataType::Text | DataType::LargeText => json.is_string(),
67 _ => false,
68 }
69 }
70 Value::Object(_) | Value::List(_) => declared == DataType::Json,
71 };
72 if matches {
73 Ok(())
74 } else {
75 Err(format!("invalid dynamic property type: {name}"))
76 }
77 }
78
79 pub fn merge(left: &Arc<Self>, right: &Arc<Self>) -> Result<Arc<Self>, String> {
81 for (name, data_type) in &right.types {
82 if left
83 .data_type(name)
84 .is_some_and(|previous| previous != *data_type)
85 {
86 return Err(name.clone());
87 }
88 }
89 if right.types.keys().all(|name| left.types.contains_key(name)) {
90 return Ok(left.clone());
91 }
92 if left.types.keys().all(|name| right.types.contains_key(name)) {
93 return Ok(right.clone());
94 }
95 let mut types = left.types.clone();
96 types.extend(right.types.clone());
97 Ok(Self::from_types(types))
98 }
99}
100
101impl std::hash::Hash for DynamicPropertyDefinitions {
102 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
103 hasher.write_u64(self.hash);
104 }
105}