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