1use ravenrs_criteria::{DateTime, Value};
4
5use crate::{EntityInfo, FieldKind, Object, ObjectSpace, Query, SpaceError};
6
7pub trait FieldValue: Sized {
9 fn kind() -> FieldKind;
11 fn to_value(&self) -> Value;
13 fn from_value(value: Value) -> Self;
15}
16
17impl FieldValue for String {
18 fn kind() -> FieldKind {
19 FieldKind::Text
20 }
21 fn to_value(&self) -> Value {
22 Value::Text(self.clone())
23 }
24 fn from_value(value: Value) -> Self {
25 if let Value::Text(text) = value { text } else { String::new() }
26 }
27}
28
29impl FieldValue for bool {
30 fn kind() -> FieldKind {
31 FieldKind::Boolean
32 }
33 fn to_value(&self) -> Value {
34 Value::Bool(*self)
35 }
36 fn from_value(value: Value) -> Self {
37 matches!(value, Value::Bool(true))
38 }
39}
40
41macro_rules! whole_numbers {
42 ($($ty:ty),*) => {$(
43 impl FieldValue for $ty {
44 fn kind() -> FieldKind {
45 FieldKind::Integer
46 }
47 fn to_value(&self) -> Value {
48 Value::Number(*self as f64)
49 }
50 fn from_value(value: Value) -> Self {
51 if let Value::Number(number) = value { number.round() as $ty } else { 0 }
52 }
53 }
54 )*};
55}
56
57whole_numbers!(i32, i64, u32, u64, usize);
58
59impl FieldValue for f64 {
60 fn kind() -> FieldKind {
61 FieldKind::Decimal(2)
62 }
63 fn to_value(&self) -> Value {
64 Value::Number(*self)
65 }
66 fn from_value(value: Value) -> Self {
67 if let Value::Number(number) = value { number } else { 0.0 }
68 }
69}
70
71impl FieldValue for DateTime {
72 fn kind() -> FieldKind {
73 FieldKind::DateTime
74 }
75 fn to_value(&self) -> Value {
76 Value::Date(*self)
77 }
78 fn from_value(value: Value) -> Self {
79 if let Value::Date(moment) = value { moment } else { DateTime { days: 0, seconds: 0 } }
80 }
81}
82
83impl<T: FieldValue> FieldValue for Option<T> {
84 fn kind() -> FieldKind {
85 T::kind()
86 }
87 fn to_value(&self) -> Value {
88 self.as_ref().map_or(Value::Null, T::to_value)
89 }
90 fn from_value(value: Value) -> Self {
91 if matches!(value, Value::Null) { None } else { Some(T::from_value(value)) }
92 }
93}
94
95pub trait BusinessObject: Sized {
128 fn entity_info() -> EntityInfo;
130 fn to_object(&self) -> Object;
132 fn from_object(object: &Object) -> Self;
134}
135
136pub trait ObjectSpaceExt {
138 fn add<T: BusinessObject>(&mut self, record: &T) -> Result<(), SpaceError>;
140 fn read<T: BusinessObject>(&self, query: &Query) -> Result<Vec<T>, SpaceError>;
142}
143
144impl<S: ObjectSpace + ?Sized> ObjectSpaceExt for S {
145 fn add<T: BusinessObject>(&mut self, record: &T) -> Result<(), SpaceError> {
146 let entity = T::entity_info();
147 let mut object = self.new_object(&entity.name)?;
148 let given = record.to_object();
149 for (name, value) in given.values() {
150 let numbered_key =
151 entity.field_named(name).is_some_and(|f| f.is_key && matches!(f.kind, FieldKind::Integer))
152 && matches!(value, Value::Number(n) if *n == 0.0);
153 if !numbered_key {
154 object.set(name, value.clone());
155 }
156 }
157 self.insert(object)
158 }
159
160 fn read<T: BusinessObject>(&self, query: &Query) -> Result<Vec<T>, SpaceError> {
161 let found = self.query(&T::entity_info().name, query)?;
162 Ok(found.iter().map(T::from_object).collect())
163 }
164}