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    /// Invoked immediately after the entity is loaded from the repository.
41    /// Used by implementations to attach runtime contexts or initialize internal states.
42    #[allow(unused_variables)]
43    fn on_loaded(&mut self, context: &dyn std::any::Any) {}
44
45    fn into_json(self) -> serde_json::Value {
46        record_to_json_value(&self.into_record())
47    }
48}
49
50#[derive(Debug, Clone, PartialEq, Default)]
51pub struct BaseEntityData {
52    pub id: u64,
53    pub version: i64,
54    pub dynamic: BTreeMap<String, Value>,
55}
56
57impl BaseEntityData {
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    pub fn with_id(mut self, id: u64) -> Self {
63        self.id = id;
64        self
65    }
66
67    pub fn with_version(mut self, version: i64) -> Self {
68        self.version = version;
69        self
70    }
71
72    pub fn with_dynamic(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
73        self.dynamic.insert(key.into(), value.into());
74        self
75    }
76
77    pub fn dynamic(&self, key: &str) -> Option<&Value> {
78        self.dynamic.get(key)
79    }
80
81    pub fn dynamic_i64(&self, key: &str) -> Option<i64> {
82        self.dynamic(key).and_then(Value::try_i64)
83    }
84
85    pub fn dynamic_u64(&self, key: &str) -> Option<u64> {
86        self.dynamic(key).and_then(Value::try_u64)
87    }
88
89    pub fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
90        self.dynamic(key).and_then(Value::try_decimal)
91    }
92
93    pub fn dynamic_f64(&self, key: &str) -> Option<f64> {
94        self.dynamic(key).and_then(Value::try_f64)
95    }
96
97    pub fn dynamic_text(&self, key: &str) -> Option<&str> {
98        self.dynamic(key).and_then(Value::try_text)
99    }
100
101    pub fn dynamic_bool(&self, key: &str) -> Option<bool> {
102        self.dynamic(key).and_then(Value::try_bool)
103    }
104
105    pub fn put_dynamic(
106        &mut self,
107        key: impl Into<String>,
108        value: impl Into<Value>,
109    ) -> Option<Value> {
110        self.dynamic.insert(key.into(), value.into())
111    }
112
113    pub fn remove_dynamic(&mut self, key: &str) -> Option<Value> {
114        self.dynamic.remove(key)
115    }
116
117    pub fn to_record(&self) -> Record {
118        let mut record = Record::new();
119        record.insert("id".to_owned(), Value::U64(self.id));
120        record.insert("version".to_owned(), Value::I64(self.version));
121        for (key, value) in &self.dynamic {
122            record.insert(key.clone(), value.clone());
123        }
124        record
125    }
126
127    pub fn from_record(record: &Record) -> Result<Self, EntityError> {
128        let id = match record.get("id") {
129            Some(Value::U64(v)) => *v,
130            Some(Value::I64(v)) if *v >= 0 => *v as u64,
131            Some(Value::Null) | None => 0,
132            other => {
133                return Err(EntityError::new(
134                    "BaseEntity",
135                    format!("invalid id field: {other:?}"),
136                ));
137            }
138        };
139
140        let version = match record.get("version") {
141            Some(Value::I64(v)) => *v,
142            Some(Value::Null) | None => 0,
143            other => {
144                return Err(EntityError::new(
145                    "BaseEntity",
146                    format!("invalid version field: {other:?}"),
147                ));
148            }
149        };
150
151        let dynamic = record
152            .iter()
153            .filter(|(key, _)| key.as_str() != "id" && key.as_str() != "version")
154            .map(|(key, value)| (key.clone(), value.clone()))
155            .collect();
156
157        Ok(Self {
158            id,
159            version,
160            dynamic,
161        })
162    }
163}
164
165pub trait BaseEntity: Entity {
166    fn base(&self) -> &BaseEntityData;
167    fn base_mut(&mut self) -> &mut BaseEntityData;
168
169    fn id(&self) -> u64 {
170        self.base().id
171    }
172
173    fn set_id(&mut self, id: u64) {
174        self.base_mut().id = id;
175    }
176
177    fn version_value(&self) -> i64 {
178        self.base().version
179    }
180
181    fn set_version(&mut self, version: i64) {
182        self.base_mut().version = version;
183    }
184
185    fn dynamic(&self, key: &str) -> Option<&Value> {
186        self.base().dynamic(key)
187    }
188
189    fn dynamic_i64(&self, key: &str) -> Option<i64> {
190        self.base().dynamic_i64(key)
191    }
192
193    fn dynamic_u64(&self, key: &str) -> Option<u64> {
194        self.base().dynamic_u64(key)
195    }
196
197    fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
198        self.base().dynamic_decimal(key)
199    }
200
201    fn dynamic_f64(&self, key: &str) -> Option<f64> {
202        self.base().dynamic_f64(key)
203    }
204
205    fn dynamic_text(&self, key: &str) -> Option<&str> {
206        self.base().dynamic_text(key)
207    }
208
209    fn dynamic_bool(&self, key: &str) -> Option<bool> {
210        self.base().dynamic_bool(key)
211    }
212
213    fn put_dynamic(&mut self, key: impl Into<String>, value: impl Into<Value>) -> Option<Value> {
214        self.base_mut().put_dynamic(key, value)
215    }
216}
217
218pub trait IdentifiableEntity: Entity {
219    fn id_value(&self) -> Value;
220}
221
222pub trait VersionedEntity: Entity {
223    fn version(&self) -> i64;
224}
225
226pub trait EntityDescriptorStore {
227    fn register_descriptor(&mut self, descriptor: EntityDescriptor);
228}
229
230#[macro_export]
231macro_rules! register_entities {
232    ($store:expr, $($entity:ty),+ $(,)?) => {{
233        $(
234            <$entity as $crate::TeaqlEntity>::register_into($store);
235        )+
236    }};
237}