1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{Decimal, EntityDescriptor, Record, Value, record_to_json_value};
4
5pub trait TeaqlEntity {
6 const ENTITY_NAME: &'static str;
7
8 fn entity_descriptor() -> EntityDescriptor;
9
10 fn register_into(store: &mut impl EntityDescriptorStore) {
11 store.register_descriptor(Self::entity_descriptor());
12 }
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct EntityError {
17 pub entity: String,
18 pub message: String,
19}
20
21impl EntityError {
22 pub fn new(entity: impl Into<String>, message: impl Into<String>) -> Self {
23 Self {
24 entity: entity.into(),
25 message: message.into(),
26 }
27 }
28}
29
30impl std::fmt::Display for EntityError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(f, "{}: {}", self.entity, self.message)
33 }
34}
35
36impl std::error::Error for EntityError {}
37
38pub trait Entity: TeaqlEntity + Sized {
39 fn from_record(record: Record) -> Result<Self, EntityError>;
40 fn into_record(self) -> Record;
41
42 fn dirty_fields(&self) -> Option<BTreeSet<String>> {
46 None
47 }
48
49 fn is_marked_as_delete(&self) -> bool {
51 false
52 }
53
54 fn is_new(&self) -> bool {
56 false
57 }
58
59 fn mark_as_new(&mut self) {}
61
62 fn get_comment(&self) -> Option<String> {
64 None
65 }
66
67 fn set_comment(&mut self, _comment: String) {}
69
70 fn audit_as(self, comment: impl Into<String>) -> Audited<Self> {
73 Audited::new(self, comment)
74 }
75
76 fn original_values(&self) -> Option<::std::collections::BTreeMap<String, Value>> {
78 None
79 }
80
81 #[allow(unused_variables)]
84 fn on_loaded(&mut self, context: &dyn std::any::Any) {}
85
86 fn into_json(self) -> serde_json::Value {
87 record_to_json_value(&self.into_record())
88 }
89}
90
91pub struct Audited<T: Entity> {
95 inner: T,
96 comment: String,
97}
98
99impl<T: Entity> Audited<T> {
100 pub fn new(entity: T, comment: impl Into<String>) -> Self {
102 let comment = comment.into();
103 assert!(
104 !comment.trim().is_empty(),
105 "audit comment must not be empty"
106 );
107 Self {
108 inner: entity,
109 comment,
110 }
111 }
112
113 pub fn entity(&self) -> &T {
115 &self.inner
116 }
117
118 pub fn entity_mut(&mut self) -> &mut T {
120 &mut self.inner
121 }
122
123 pub fn into_entity(self) -> T {
125 let mut entity = self.inner;
126 entity.set_comment(self.comment);
127 entity
128 }
129
130 pub fn get_comment(&self) -> &str {
132 &self.comment
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Default)]
137pub struct BaseEntityData {
138 pub id: u64,
139 pub version: i64,
140 pub dynamic: BTreeMap<String, Value>,
141}
142
143impl BaseEntityData {
144 pub fn new() -> Self {
145 Self::default()
146 }
147
148 pub fn with_id(mut self, id: u64) -> Self {
149 self.id = id;
150 self
151 }
152
153 pub fn with_version(mut self, version: i64) -> Self {
154 self.version = version;
155 self
156 }
157
158 pub fn with_dynamic(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
159 self.dynamic.insert(key.into(), value.into());
160 self
161 }
162
163 pub fn dynamic(&self, key: &str) -> Option<&Value> {
164 self.dynamic.get(key)
165 }
166
167 pub fn dynamic_i64(&self, key: &str) -> Option<i64> {
168 self.dynamic(key).and_then(Value::try_i64)
169 }
170
171 pub fn dynamic_u64(&self, key: &str) -> Option<u64> {
172 self.dynamic(key).and_then(Value::try_u64)
173 }
174
175 pub fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
176 self.dynamic(key).and_then(Value::try_decimal)
177 }
178
179 pub fn dynamic_f64(&self, key: &str) -> Option<f64> {
180 self.dynamic(key).and_then(Value::try_f64)
181 }
182
183 pub fn dynamic_text(&self, key: &str) -> Option<&str> {
184 self.dynamic(key).and_then(Value::try_text)
185 }
186
187 pub fn dynamic_bool(&self, key: &str) -> Option<bool> {
188 self.dynamic(key).and_then(Value::try_bool)
189 }
190
191 pub fn put_dynamic(
192 &mut self,
193 key: impl Into<String>,
194 value: impl Into<Value>,
195 ) -> Option<Value> {
196 self.dynamic.insert(key.into(), value.into())
197 }
198
199 pub fn remove_dynamic(&mut self, key: &str) -> Option<Value> {
200 self.dynamic.remove(key)
201 }
202
203 pub fn to_record(&self) -> Record {
204 let mut record = Record::new();
205 record.insert("id".to_owned(), Value::U64(self.id));
206 record.insert("version".to_owned(), Value::I64(self.version));
207 for (key, value) in &self.dynamic {
208 record.insert(key.clone(), value.clone());
209 }
210 record
211 }
212
213 pub fn from_record(record: &Record) -> Result<Self, EntityError> {
214 let id = match record.get("id") {
215 Some(Value::U64(v)) => *v,
216 Some(Value::I64(v)) if *v >= 0 => *v as u64,
217 Some(Value::Null) | None => 0,
218 other => {
219 return Err(EntityError::new(
220 "BaseEntity",
221 format!("invalid id field: {other:?}"),
222 ));
223 }
224 };
225
226 let version = match record.get("version") {
227 Some(Value::I64(v)) => *v,
228 Some(Value::Null) | None => 0,
229 other => {
230 return Err(EntityError::new(
231 "BaseEntity",
232 format!("invalid version field: {other:?}"),
233 ));
234 }
235 };
236
237 let dynamic = record
238 .iter()
239 .filter(|(key, _)| key.as_str() != "id" && key.as_str() != "version")
240 .map(|(key, value)| (key.clone(), value.clone()))
241 .collect();
242
243 Ok(Self {
244 id,
245 version,
246 dynamic,
247 })
248 }
249}
250
251pub trait BaseEntity: Entity {
252 fn base(&self) -> &BaseEntityData;
253 fn base_mut(&mut self) -> &mut BaseEntityData;
254
255 fn id(&self) -> u64 {
256 self.base().id
257 }
258
259 fn set_id(&mut self, id: u64) {
260 self.base_mut().id = id;
261 }
262
263 fn version_value(&self) -> i64 {
264 self.base().version
265 }
266
267 fn set_version(&mut self, version: i64) {
268 self.base_mut().version = version;
269 }
270
271 fn dynamic(&self, key: &str) -> Option<&Value> {
272 self.base().dynamic(key)
273 }
274
275 fn dynamic_i64(&self, key: &str) -> Option<i64> {
276 self.base().dynamic_i64(key)
277 }
278
279 fn dynamic_u64(&self, key: &str) -> Option<u64> {
280 self.base().dynamic_u64(key)
281 }
282
283 fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
284 self.base().dynamic_decimal(key)
285 }
286
287 fn dynamic_f64(&self, key: &str) -> Option<f64> {
288 self.base().dynamic_f64(key)
289 }
290
291 fn dynamic_text(&self, key: &str) -> Option<&str> {
292 self.base().dynamic_text(key)
293 }
294
295 fn dynamic_bool(&self, key: &str) -> Option<bool> {
296 self.base().dynamic_bool(key)
297 }
298
299 fn put_dynamic(&mut self, key: impl Into<String>, value: impl Into<Value>) -> Option<Value> {
300 self.base_mut().put_dynamic(key, value)
301 }
302}
303
304pub trait IdentifiableEntity: Entity {
305 fn id_value(&self) -> Value;
306}
307
308pub trait VersionedEntity: Entity {
309 fn version(&self) -> i64;
310}
311
312pub trait TeaqlBoxedRelations: Sized {
313 fn extend_descriptor(descriptor: &mut EntityDescriptor);
314 fn extract_from_record(record: &Record) -> Result<Self, EntityError>;
315 fn inject_into_record(self, record: &mut Record);
316}
317
318impl<T: TeaqlBoxedRelations> TeaqlBoxedRelations for Box<T> {
319 fn extend_descriptor(descriptor: &mut EntityDescriptor) {
320 T::extend_descriptor(descriptor);
321 }
322 fn extract_from_record(record: &Record) -> Result<Self, EntityError> {
323 Ok(Box::new(T::extract_from_record(record)?))
324 }
325 fn inject_into_record(self, record: &mut Record) {
326 (*self).inject_into_record(record);
327 }
328}
329
330pub trait EntityDescriptorStore {
331 fn register_descriptor(&mut self, descriptor: EntityDescriptor);
332}
333
334#[macro_export]
335macro_rules! register_entities {
336 ($store:expr, $($entity:ty),+ $(,)?) => {{
337 $(
338 <$entity as $crate::TeaqlEntity>::register_into($store);
339 )+
340 }};
341}