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 comment(&self) -> Option<String> {
62 None
63 }
64
65 fn set_comment(&mut self, _comment: String) {}
67
68 fn original_values(&self) -> Option<::std::collections::BTreeMap<String, Value>> {
70 None
71 }
72
73 #[allow(unused_variables)]
76 fn on_loaded(&mut self, context: &dyn std::any::Any) {}
77
78 fn into_json(self) -> serde_json::Value {
79 record_to_json_value(&self.into_record())
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Default)]
84pub struct BaseEntityData {
85 pub id: u64,
86 pub version: i64,
87 pub dynamic: BTreeMap<String, Value>,
88}
89
90impl BaseEntityData {
91 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn with_id(mut self, id: u64) -> Self {
96 self.id = id;
97 self
98 }
99
100 pub fn with_version(mut self, version: i64) -> Self {
101 self.version = version;
102 self
103 }
104
105 pub fn with_dynamic(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
106 self.dynamic.insert(key.into(), value.into());
107 self
108 }
109
110 pub fn dynamic(&self, key: &str) -> Option<&Value> {
111 self.dynamic.get(key)
112 }
113
114 pub fn dynamic_i64(&self, key: &str) -> Option<i64> {
115 self.dynamic(key).and_then(Value::try_i64)
116 }
117
118 pub fn dynamic_u64(&self, key: &str) -> Option<u64> {
119 self.dynamic(key).and_then(Value::try_u64)
120 }
121
122 pub fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
123 self.dynamic(key).and_then(Value::try_decimal)
124 }
125
126 pub fn dynamic_f64(&self, key: &str) -> Option<f64> {
127 self.dynamic(key).and_then(Value::try_f64)
128 }
129
130 pub fn dynamic_text(&self, key: &str) -> Option<&str> {
131 self.dynamic(key).and_then(Value::try_text)
132 }
133
134 pub fn dynamic_bool(&self, key: &str) -> Option<bool> {
135 self.dynamic(key).and_then(Value::try_bool)
136 }
137
138 pub fn put_dynamic(
139 &mut self,
140 key: impl Into<String>,
141 value: impl Into<Value>,
142 ) -> Option<Value> {
143 self.dynamic.insert(key.into(), value.into())
144 }
145
146 pub fn remove_dynamic(&mut self, key: &str) -> Option<Value> {
147 self.dynamic.remove(key)
148 }
149
150 pub fn to_record(&self) -> Record {
151 let mut record = Record::new();
152 record.insert("id".to_owned(), Value::U64(self.id));
153 record.insert("version".to_owned(), Value::I64(self.version));
154 for (key, value) in &self.dynamic {
155 record.insert(key.clone(), value.clone());
156 }
157 record
158 }
159
160 pub fn from_record(record: &Record) -> Result<Self, EntityError> {
161 let id = match record.get("id") {
162 Some(Value::U64(v)) => *v,
163 Some(Value::I64(v)) if *v >= 0 => *v as u64,
164 Some(Value::Null) | None => 0,
165 other => {
166 return Err(EntityError::new(
167 "BaseEntity",
168 format!("invalid id field: {other:?}"),
169 ));
170 }
171 };
172
173 let version = match record.get("version") {
174 Some(Value::I64(v)) => *v,
175 Some(Value::Null) | None => 0,
176 other => {
177 return Err(EntityError::new(
178 "BaseEntity",
179 format!("invalid version field: {other:?}"),
180 ));
181 }
182 };
183
184 let dynamic = record
185 .iter()
186 .filter(|(key, _)| key.as_str() != "id" && key.as_str() != "version")
187 .map(|(key, value)| (key.clone(), value.clone()))
188 .collect();
189
190 Ok(Self {
191 id,
192 version,
193 dynamic,
194 })
195 }
196}
197
198pub trait BaseEntity: Entity {
199 fn base(&self) -> &BaseEntityData;
200 fn base_mut(&mut self) -> &mut BaseEntityData;
201
202 fn id(&self) -> u64 {
203 self.base().id
204 }
205
206 fn set_id(&mut self, id: u64) {
207 self.base_mut().id = id;
208 }
209
210 fn version_value(&self) -> i64 {
211 self.base().version
212 }
213
214 fn set_version(&mut self, version: i64) {
215 self.base_mut().version = version;
216 }
217
218 fn dynamic(&self, key: &str) -> Option<&Value> {
219 self.base().dynamic(key)
220 }
221
222 fn dynamic_i64(&self, key: &str) -> Option<i64> {
223 self.base().dynamic_i64(key)
224 }
225
226 fn dynamic_u64(&self, key: &str) -> Option<u64> {
227 self.base().dynamic_u64(key)
228 }
229
230 fn dynamic_decimal(&self, key: &str) -> Option<Decimal> {
231 self.base().dynamic_decimal(key)
232 }
233
234 fn dynamic_f64(&self, key: &str) -> Option<f64> {
235 self.base().dynamic_f64(key)
236 }
237
238 fn dynamic_text(&self, key: &str) -> Option<&str> {
239 self.base().dynamic_text(key)
240 }
241
242 fn dynamic_bool(&self, key: &str) -> Option<bool> {
243 self.base().dynamic_bool(key)
244 }
245
246 fn put_dynamic(&mut self, key: impl Into<String>, value: impl Into<Value>) -> Option<Value> {
247 self.base_mut().put_dynamic(key, value)
248 }
249}
250
251pub trait IdentifiableEntity: Entity {
252 fn id_value(&self) -> Value;
253}
254
255pub trait VersionedEntity: Entity {
256 fn version(&self) -> i64;
257}
258
259pub trait EntityDescriptorStore {
260 fn register_descriptor(&mut self, descriptor: EntityDescriptor);
261}
262
263#[macro_export]
264macro_rules! register_entities {
265 ($store:expr, $($entity:ty),+ $(,)?) => {{
266 $(
267 <$entity as $crate::TeaqlEntity>::register_into($store);
268 )+
269 }};
270}