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