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