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 #[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}