1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{
4 CompactRow, Decimal, EntityDescriptor, EntitySnapshot, MutationIntent, MutationValues, Value,
5 record_to_json_value,
6};
7
8#[doc(hidden)]
10#[derive(Default)]
11pub struct EntityJsonTraversal {
12 path: Vec<(&'static str, usize)>,
13}
14
15impl EntityJsonTraversal {
16 pub fn enter(&mut self, entity: &'static str, address: usize) -> bool {
17 let expand = self.path.len() < 128 && !self.path.contains(&(entity, address));
18 self.path.push((entity, address));
19 expand
20 }
21
22 pub fn leave(&mut self) {
23 self.path.pop();
24 }
25}
26
27pub trait TeaqlEntity {
28 const ENTITY_NAME: &'static str;
29
30 fn entity_descriptor() -> EntityDescriptor;
31
32 fn field_layout() -> Result<Option<std::sync::Arc<crate::FieldLayout>>, EntityError> {
34 Ok(None)
35 }
36
37 fn register_into(store: &mut impl EntityDescriptorStore) {
38 store.register_descriptor(Self::entity_descriptor());
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct EntityError {
44 pub entity: String,
45 pub message: String,
46}
47
48impl EntityError {
49 pub fn new(entity: impl Into<String>, message: impl Into<String>) -> Self {
50 Self {
51 entity: entity.into(),
52 message: message.into(),
53 }
54 }
55}
56
57impl std::fmt::Display for EntityError {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 write!(f, "{}: {}", self.entity, self.message)
60 }
61}
62
63impl std::error::Error for EntityError {}
64
65pub trait Entity: TeaqlEntity + Sized {
66 fn dynamic_property_type(&self, key: &str) -> Option<crate::DataType> {
68 let state = self.loaded_state_snapshot()?;
69 state.dynamic_property_definitions()?.data_type(key)
70 }
71 #[doc(hidden)]
73 fn supports_dynamic_property_load() -> bool {
74 false
75 }
76
77 fn dynamic_property(&self, _key: &str) -> Option<&Value> {
81 None
82 }
83
84 fn has_dynamic_property(&self, _key: &str) -> bool {
88 false
89 }
90
91 fn dynamic_field_values(&self) -> Option<&crate::dynamic_fields::DynamicFieldValues> {
93 None
94 }
95
96 fn has_pending_dynamic_mutations(&self) -> bool {
97 false
98 }
99
100 #[doc(hidden)]
102 fn __teaql_runtime_state_any(&self) -> Option<&dyn std::any::Any> {
103 None
104 }
105
106 fn update_dynamic_field(
107 &mut self,
108 code: &str,
109 _value: Value,
110 ) -> Result<(), crate::dynamic_fields::DynamicFieldError> {
111 Err(crate::dynamic_fields::DynamicFieldError {
112 code: "DYNAMIC_FIELD_CARRIER_MISSING",
113 field: code.to_owned(),
114 })
115 }
116
117 fn delete_dynamic_field(
118 &mut self,
119 code: &str,
120 ) -> Result<(), crate::dynamic_fields::DynamicFieldError> {
121 Err(crate::dynamic_fields::DynamicFieldError {
122 code: "DYNAMIC_FIELD_CARRIER_MISSING",
123 field: code.to_owned(),
124 })
125 }
126
127 fn supports_dynamic_field_load() -> bool {
128 false
129 }
130
131 fn loaded_state_snapshot(&self) -> Option<std::sync::Arc<crate::LoadedSnapshot>> {
132 None
133 }
134
135 #[doc(hidden)]
137 fn install_loaded_dynamic_fields(
138 &mut self,
139 _values: crate::dynamic_fields::DynamicFieldValues,
140 _state: std::sync::Arc<crate::LoadedSnapshot>,
141 ) -> Result<(), EntityError> {
142 Err(EntityError::new(
143 Self::ENTITY_NAME,
144 "entity lacks the runtime-owned indexed dynamic-field carrier",
145 ))
146 }
147
148 fn from_compact_row(row: CompactRow) -> Result<Self, EntityError>;
149
150 fn from_compact_row_with_context(
151 row: CompactRow,
152 context: &dyn std::any::Any,
153 ) -> Result<Self, EntityError> {
154 let mut entity = Self::from_compact_row(row)?;
155 entity.on_loaded(context);
156 Ok(entity)
157 }
158
159 fn into_values(self) -> MutationValues;
160
161 #[doc(hidden)]
164 fn into_json_values(self) -> BTreeMap<String, Value> {
165 self.into_values().into()
166 }
167
168 #[doc(hidden)]
171 fn borrowed_json(&self, _traversal: &mut EntityJsonTraversal) -> Option<serde_json::Value> {
172 None
173 }
174
175 fn is_field_loaded(&self, _field: &str) -> bool {
179 true
180 }
181
182 fn set_checker_loaded_fields(&mut self, _fields: BTreeSet<String>) {}
185
186 fn set_checker_dirty_fields(&mut self, _fields: BTreeSet<String>, _values: &MutationValues) {}
190
191 fn dirty_fields(&self) -> Option<BTreeSet<String>> {
195 None
196 }
197
198 fn is_marked_as_delete(&self) -> bool {
200 false
201 }
202
203 fn is_new(&self) -> bool {
205 false
206 }
207
208 fn mark_as_new(&mut self) {}
210
211 fn get_comment(&self) -> Option<String> {
213 None
214 }
215
216 fn set_comment(&mut self, _comment: String) {}
218
219 fn audit_as(self, comment: impl Into<String>) -> Audited<Self> {
222 Audited::new(self, comment)
223 }
224
225 fn original_values(&self) -> Option<EntitySnapshot> {
227 None
228 }
229
230 #[allow(unused_variables)]
233 fn on_loaded(&mut self, context: &dyn std::any::Any) {}
234
235 fn into_json(self) -> serde_json::Value {
236 if let Some(json) = self.borrowed_json(&mut EntityJsonTraversal::default()) {
237 return json;
238 }
239 let extensions: serde_json::Map<String, serde_json::Value> = self
242 .dynamic_field_values()
243 .map(|fields| {
244 fields
245 .values()
246 .iter()
247 .map(|(code, value)| (format!("#{code}"), value.to_json_value()))
248 .collect()
249 })
250 .unwrap_or_default();
251 let mut values = self.into_json_values();
252 for key in [
256 "_comment",
257 "_dirty_fields",
258 "_original_values",
259 "_is_new",
260 "_is_deleted",
261 ] {
262 values.remove(key);
263 }
264 let mut json = record_to_json_value(&values);
265 json.as_object_mut()
266 .expect("entity serialization is an object")
267 .extend(extensions);
268 json
269 }
270}
271
272pub struct Audited<T: Entity> {
276 inner: T,
277 comment: MutationIntent,
278}
279
280impl<T: Entity> Audited<T> {
281 pub fn new(entity: T, comment: impl Into<String>) -> Self {
288 let comment = MutationIntent::new(comment).unwrap_or_else(|error| panic!("{error}"));
289 Self {
290 inner: entity,
291 comment,
292 }
293 }
294
295 pub fn entity(&self) -> &T {
297 &self.inner
298 }
299
300 pub fn entity_mut(&mut self) -> &mut T {
302 &mut self.inner
303 }
304
305 pub fn into_entity(self) -> T {
307 let mut entity = self.inner;
308 entity.set_comment(self.comment.into_comment());
309 entity
310 }
311
312 pub fn get_comment(&self) -> &str {
314 self.comment.comment()
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Default)]
319pub struct BaseEntityData {
320 pub id: u64,
321 pub version: i64,
322 pub dynamic: BTreeMap<String, Value>,
323}
324
325impl BaseEntityData {
326 pub fn new() -> Self {
327 Self::default()
328 }
329
330 pub fn with_id(mut self, id: u64) -> Self {
331 self.id = id;
332 self
333 }
334
335 pub fn with_version(mut self, version: i64) -> Self {
336 self.version = version;
337 self
338 }
339
340 pub fn with_dynamic(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
341 self.dynamic.insert(key.into(), value.into());
342 self
343 }
344
345 pub fn dynamic(&self, key: &str) -> Option<&Value> {
346 self.dynamic.get(key)
347 }
348
349 pub fn dynamic_i64(&self, key: &str) -> Option<i64> {
350 self.dynamic(key).and_then(Value::try_i64)
351 }
352
353 pub fn dynamic_u64(&self, key: &str) -> Option<u64> {
354 self.dynamic(key).and_then(Value::try_u64)
355 }
356
357 pub fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
358 self.dynamic(key).and_then(Value::try_decimal)
359 }
360
361 pub fn dynamic_f64(&self, key: &str) -> Option<f64> {
362 self.dynamic(key).and_then(Value::try_f64)
363 }
364
365 pub fn dynamic_text(&self, key: &str) -> Option<&str> {
366 self.dynamic(key).and_then(Value::try_text)
367 }
368
369 pub fn dynamic_bool(&self, key: &str) -> Option<bool> {
370 self.dynamic(key).and_then(Value::try_bool)
371 }
372
373 pub fn put_dynamic(
374 &mut self,
375 key: impl Into<String>,
376 value: impl Into<Value>,
377 ) -> Option<Value> {
378 self.dynamic.insert(key.into(), value.into())
379 }
380
381 pub fn remove_dynamic(&mut self, key: &str) -> Option<Value> {
382 self.dynamic.remove(key)
383 }
384
385 pub fn to_values_map(&self) -> BTreeMap<String, Value> {
386 let mut values = BTreeMap::new();
387 values.insert("id".to_owned(), Value::U64(self.id));
388 values.insert("version".to_owned(), Value::I64(self.version));
389 for (key, value) in &self.dynamic {
390 values.insert(key.clone(), value.clone());
391 }
392 values
393 }
394
395 pub fn from_values_map(values: &BTreeMap<String, Value>) -> Result<Self, EntityError> {
396 let id = match values.get("id") {
397 Some(Value::U64(v)) => *v,
398 Some(Value::I64(v)) if *v >= 0 => *v as u64,
399 Some(Value::Null) | None => 0,
400 other => {
401 return Err(EntityError::new(
402 "BaseEntity",
403 format!("invalid id field: {other:?}"),
404 ));
405 }
406 };
407
408 let version = match values.get("version") {
409 Some(Value::I64(v)) => *v,
410 Some(Value::Null) | None => 0,
411 other => {
412 return Err(EntityError::new(
413 "BaseEntity",
414 format!("invalid version field: {other:?}"),
415 ));
416 }
417 };
418
419 let dynamic = values
420 .iter()
421 .filter(|(key, _)| key.as_str() != "id" && key.as_str() != "version")
422 .map(|(key, value)| (key.clone(), value.clone()))
423 .collect();
424
425 Ok(Self {
426 id,
427 version,
428 dynamic,
429 })
430 }
431}
432
433pub trait BaseEntity: Entity {
434 fn base(&self) -> &BaseEntityData;
435 fn base_mut(&mut self) -> &mut BaseEntityData;
436
437 fn id(&self) -> u64 {
438 self.base().id
439 }
440
441 fn set_id(&mut self, id: u64) {
442 self.base_mut().id = id;
443 }
444
445 fn version_value(&self) -> i64 {
446 self.base().version
447 }
448
449 fn set_version(&mut self, version: i64) {
450 self.base_mut().version = version;
451 }
452
453 fn dynamic(&self, key: &str) -> Option<&Value> {
454 self.base().dynamic(key)
455 }
456
457 fn dynamic_i64(&self, key: &str) -> Option<i64> {
458 self.base().dynamic_i64(key)
459 }
460
461 fn dynamic_u64(&self, key: &str) -> Option<u64> {
462 self.base().dynamic_u64(key)
463 }
464
465 fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
466 self.base().dynamic_decimal(key)
467 }
468
469 fn dynamic_f64(&self, key: &str) -> Option<f64> {
470 self.base().dynamic_f64(key)
471 }
472
473 fn dynamic_text(&self, key: &str) -> Option<&str> {
474 self.base().dynamic_text(key)
475 }
476
477 fn dynamic_bool(&self, key: &str) -> Option<bool> {
478 self.base().dynamic_bool(key)
479 }
480
481 fn put_dynamic(&mut self, key: impl Into<String>, value: impl Into<Value>) -> Option<Value> {
482 self.base_mut().put_dynamic(key, value)
483 }
484}
485
486pub trait IdentifiableEntity: Entity {
487 fn id_value(&self) -> Value;
488}
489
490pub trait VersionedEntity: Entity {
491 fn version(&self) -> i64;
492}
493
494pub trait TeaqlBoxedRelations: Sized {
495 fn extend_descriptor(descriptor: &mut EntityDescriptor);
496 fn extract_from_values(values: &CompactRow) -> Result<Self, EntityError>;
497 fn inject_into_values(self, values: &mut BTreeMap<String, Value>);
498 #[doc(hidden)]
499 fn inject_into_json_values(
500 self,
501 values: &mut BTreeMap<String, Value>,
502 _loaded: Option<&crate::eval::LoadState>,
503 ) {
504 self.inject_into_values(values);
505 }
506}
507
508impl<T: TeaqlBoxedRelations> TeaqlBoxedRelations for Box<T> {
509 fn extend_descriptor(descriptor: &mut EntityDescriptor) {
510 T::extend_descriptor(descriptor);
511 }
512 fn extract_from_values(values: &CompactRow) -> Result<Self, EntityError> {
513 Ok(Box::new(T::extract_from_values(values)?))
514 }
515 fn inject_into_values(self, values: &mut BTreeMap<String, Value>) {
516 (*self).inject_into_values(values);
517 }
518 fn inject_into_json_values(
519 self,
520 values: &mut BTreeMap<String, Value>,
521 loaded: Option<&crate::eval::LoadState>,
522 ) {
523 (*self).inject_into_json_values(values, loaded);
524 }
525}
526
527pub trait EntityDescriptorStore {
528 fn register_descriptor(&mut self, descriptor: EntityDescriptor);
529}
530
531#[macro_export]
532macro_rules! register_entities {
533 ($store:expr, $($entity:ty),+ $(,)?) => {{
534 $(
535 <$entity as $crate::TeaqlEntity>::register_into($store);
536 )+
537 }};
538}