Skip to main content

teaql_core/
entity.rs

1use std::collections::BTreeMap;
2
3use crate::{Decimal, EntityDescriptor, Record, Value, record_to_json_value};
4
5pub trait TeaqlEntity {
6    fn entity_descriptor() -> EntityDescriptor;
7
8    fn register_into(store: &mut impl EntityDescriptorStore) {
9        store.register_descriptor(Self::entity_descriptor());
10    }
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct EntityError {
15    pub entity: String,
16    pub message: String,
17}
18
19impl EntityError {
20    pub fn new(entity: impl Into<String>, message: impl Into<String>) -> Self {
21        Self {
22            entity: entity.into(),
23            message: message.into(),
24        }
25    }
26}
27
28impl std::fmt::Display for EntityError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{}: {}", self.entity, self.message)
31    }
32}
33
34impl std::error::Error for EntityError {}
35
36pub trait Entity: TeaqlEntity + Sized {
37    fn from_record(record: Record) -> Result<Self, EntityError>;
38    fn into_record(self) -> Record;
39
40    fn into_json(self) -> serde_json::Value {
41        record_to_json_value(&self.into_record())
42    }
43}
44
45#[derive(Debug, Clone, PartialEq, Default)]
46pub struct BaseEntityData {
47    pub id: u64,
48    pub version: i64,
49    pub dynamic: BTreeMap<String, Value>,
50}
51
52impl BaseEntityData {
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    pub fn with_id(mut self, id: u64) -> Self {
58        self.id = id;
59        self
60    }
61
62    pub fn with_version(mut self, version: i64) -> Self {
63        self.version = version;
64        self
65    }
66
67    pub fn with_dynamic(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
68        self.dynamic.insert(key.into(), value.into());
69        self
70    }
71
72    pub fn dynamic(&self, key: &str) -> Option<&Value> {
73        self.dynamic.get(key)
74    }
75
76    pub fn dynamic_i64(&self, key: &str) -> Option<i64> {
77        self.dynamic(key).and_then(Value::try_i64)
78    }
79
80    pub fn dynamic_u64(&self, key: &str) -> Option<u64> {
81        self.dynamic(key).and_then(Value::try_u64)
82    }
83
84    pub fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
85        self.dynamic(key).and_then(Value::try_decimal)
86    }
87
88    pub fn dynamic_f64(&self, key: &str) -> Option<f64> {
89        self.dynamic(key).and_then(Value::try_f64)
90    }
91
92    pub fn dynamic_text(&self, key: &str) -> Option<&str> {
93        self.dynamic(key).and_then(Value::try_text)
94    }
95
96    pub fn dynamic_bool(&self, key: &str) -> Option<bool> {
97        self.dynamic(key).and_then(Value::try_bool)
98    }
99
100    pub fn put_dynamic(
101        &mut self,
102        key: impl Into<String>,
103        value: impl Into<Value>,
104    ) -> Option<Value> {
105        self.dynamic.insert(key.into(), value.into())
106    }
107
108    pub fn remove_dynamic(&mut self, key: &str) -> Option<Value> {
109        self.dynamic.remove(key)
110    }
111
112    pub fn to_record(&self) -> Record {
113        let mut record = Record::new();
114        record.insert("id".to_owned(), Value::U64(self.id));
115        record.insert("version".to_owned(), Value::I64(self.version));
116        for (key, value) in &self.dynamic {
117            record.insert(key.clone(), value.clone());
118        }
119        record
120    }
121
122    pub fn from_record(record: &Record) -> Result<Self, EntityError> {
123        let id = match record.get("id") {
124            Some(Value::U64(v)) => *v,
125            Some(Value::I64(v)) if *v >= 0 => *v as u64,
126            Some(Value::Null) | None => 0,
127            other => {
128                return Err(EntityError::new(
129                    "BaseEntity",
130                    format!("invalid id field: {other:?}"),
131                ));
132            }
133        };
134
135        let version = match record.get("version") {
136            Some(Value::I64(v)) => *v,
137            Some(Value::Null) | None => 0,
138            other => {
139                return Err(EntityError::new(
140                    "BaseEntity",
141                    format!("invalid version field: {other:?}"),
142                ));
143            }
144        };
145
146        let dynamic = record
147            .iter()
148            .filter(|(key, _)| key.as_str() != "id" && key.as_str() != "version")
149            .map(|(key, value)| (key.clone(), value.clone()))
150            .collect();
151
152        Ok(Self {
153            id,
154            version,
155            dynamic,
156        })
157    }
158}
159
160pub trait BaseEntity: Entity {
161    fn base(&self) -> &BaseEntityData;
162    fn base_mut(&mut self) -> &mut BaseEntityData;
163
164    fn id(&self) -> u64 {
165        self.base().id
166    }
167
168    fn set_id(&mut self, id: u64) {
169        self.base_mut().id = id;
170    }
171
172    fn version_value(&self) -> i64 {
173        self.base().version
174    }
175
176    fn set_version(&mut self, version: i64) {
177        self.base_mut().version = version;
178    }
179
180    fn dynamic(&self, key: &str) -> Option<&Value> {
181        self.base().dynamic(key)
182    }
183
184    fn dynamic_i64(&self, key: &str) -> Option<i64> {
185        self.base().dynamic_i64(key)
186    }
187
188    fn dynamic_u64(&self, key: &str) -> Option<u64> {
189        self.base().dynamic_u64(key)
190    }
191
192    fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
193        self.base().dynamic_decimal(key)
194    }
195
196    fn dynamic_f64(&self, key: &str) -> Option<f64> {
197        self.base().dynamic_f64(key)
198    }
199
200    fn dynamic_text(&self, key: &str) -> Option<&str> {
201        self.base().dynamic_text(key)
202    }
203
204    fn dynamic_bool(&self, key: &str) -> Option<bool> {
205        self.base().dynamic_bool(key)
206    }
207
208    fn put_dynamic(&mut self, key: impl Into<String>, value: impl Into<Value>) -> Option<Value> {
209        self.base_mut().put_dynamic(key, value)
210    }
211}
212
213pub trait IdentifiableEntity: Entity {
214    fn id_value(&self) -> Value;
215}
216
217pub trait VersionedEntity: Entity {
218    fn version(&self) -> i64;
219}
220
221pub trait EntityDescriptorStore {
222    fn register_descriptor(&mut self, descriptor: EntityDescriptor);
223}
224
225#[macro_export]
226macro_rules! register_entities {
227    ($store:expr, $($entity:ty),+ $(,)?) => {{
228        $(
229            <$entity as $crate::TeaqlEntity>::register_into($store);
230        )+
231    }};
232}