1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4const SQL_NULL_ARRAY_ELEMENT_KEY: &str = "__reinhardt_sql_null_array_element";
5const JSON_ARRAY_ELEMENT_KEY: &str = "__reinhardt_json_array_element";
6
7#[doc(hidden)]
8pub type DatabaseValue = serde_json::Value;
9
10#[doc(hidden)]
11pub type DatabaseSerializationError = serde_json::Error;
12
13#[doc(hidden)]
14pub fn serialize_model_database_value<T: Serialize>(
15 value: &T,
16) -> Result<DatabaseValue, DatabaseSerializationError> {
17 serde_json::to_value(value)
18}
19
20#[doc(hidden)]
22pub fn serialize_nullable_json_array(values: &[Option<serde_json::Value>]) -> serde_json::Value {
23 serde_json::Value::Array(
24 values
25 .iter()
26 .map(|value| {
27 value.as_ref().map_or_else(
28 || {
29 let mut marker = serde_json::Map::new();
30 marker.insert(
31 SQL_NULL_ARRAY_ELEMENT_KEY.to_owned(),
32 serde_json::Value::Bool(true),
33 );
34 serde_json::Value::Object(marker)
35 },
36 |value| {
37 let mut element = serde_json::Map::new();
38 element.insert(JSON_ARRAY_ELEMENT_KEY.to_owned(), value.clone());
39 serde_json::Value::Object(element)
40 },
41 )
42 })
43 .collect(),
44 )
45}
46
47#[doc(hidden)]
49pub fn serialize_nullable_json_array_option(
50 values: &Option<Vec<Option<serde_json::Value>>>,
51) -> serde_json::Value {
52 values.as_ref().map_or(serde_json::Value::Null, |values| {
53 serialize_nullable_json_array(values)
54 })
55}
56
57pub(crate) fn is_sql_null_array_element(value: &serde_json::Value) -> bool {
58 value.as_object().is_some_and(|object| {
59 object.len() == 1
60 && object
61 .get(SQL_NULL_ARRAY_ELEMENT_KEY)
62 .is_some_and(|value| value == &serde_json::Value::Bool(true))
63 })
64}
65
66pub(crate) fn unwrap_json_array_element(value: &serde_json::Value) -> Option<&serde_json::Value> {
67 value.as_object().and_then(|object| {
68 (object.len() == 1)
69 .then(|| object.get(JSON_ARRAY_ELEMENT_KEY))
70 .flatten()
71 })
72}
73
74pub trait FieldSelector: Clone {
79 fn with_alias(self, alias: &str) -> Self;
84}
85
86#[doc(hidden)]
93pub fn deserialize_primary_key_from_str<T>(value: &str) -> Result<T, serde_json::Error>
94where
95 T: serde::de::DeserializeOwned,
96{
97 serde_json::from_value(serde_json::Value::String(value.to_owned()))
98 .or_else(|_| serde_json::from_str(value))
99}
100
101fn is_timezone_aware_datetime_type(type_name: &str) -> bool {
102 type_name.starts_with("chrono::DateTime<")
103 || type_name.starts_with("chrono::datetime::DateTime<")
104}
105
106fn is_decimal_type(type_name: &str) -> bool {
107 matches!(
108 type_name,
109 "rust_decimal::Decimal" | "rust_decimal::decimal::Decimal"
110 )
111}
112
113#[doc(hidden)]
119pub fn deserialize_primary_key_filter_value_from_str<T>(
120 value: &str,
121) -> Result<Option<super::query::FilterValue>, serde_json::Error>
122where
123 T: serde::de::DeserializeOwned,
124{
125 if std::any::type_name::<T>() == std::any::type_name::<uuid::Uuid>() {
126 return deserialize_primary_key_from_str::<uuid::Uuid>(value)
127 .map(super::query::FilterValue::Uuid)
128 .map(Some);
129 }
130
131 if is_timezone_aware_datetime_type(std::any::type_name::<T>()) {
132 return serde_json::from_value::<chrono::DateTime<chrono::Utc>>(serde_json::Value::String(
133 value.to_owned(),
134 ))
135 .map(super::query::FilterValue::Timestamp)
136 .map(Some);
137 }
138
139 if is_decimal_type(std::any::type_name::<T>()) {
140 return deserialize_primary_key_from_str::<rust_decimal::Decimal>(value)
141 .map(super::query::FilterValue::Decimal)
142 .map(Some);
143 }
144
145 if std::any::type_name::<T>() == std::any::type_name::<chrono::NaiveDate>() {
146 return deserialize_primary_key_from_str::<chrono::NaiveDate>(value)
147 .map(super::query::FilterValue::Date)
148 .map(Some);
149 }
150
151 if std::any::type_name::<T>() == std::any::type_name::<chrono::NaiveTime>() {
152 return deserialize_primary_key_from_str::<chrono::NaiveTime>(value)
153 .map(super::query::FilterValue::Time)
154 .map(Some);
155 }
156
157 Ok(None)
158}
159
160#[doc(hidden)]
162pub fn filter_value_from_field_type(
163 field_type: &str,
164 value: &str,
165) -> reinhardt_core::exception::Result<super::query::FilterValue> {
166 use reinhardt_core::exception::Error;
167
168 let invalid = || Error::Validation(format!("invalid {field_type} value: {value}"));
169 match field_type.rsplit('.').next() {
170 Some("BooleanField") => value
171 .parse()
172 .map(super::query::FilterValue::Boolean)
173 .map_err(|_| invalid()),
174 Some("IntegerField") | Some("AutoField") => value
175 .parse::<i32>()
176 .map(|value| super::query::FilterValue::Integer(i64::from(value)))
177 .map_err(|_| invalid()),
178 Some("BigIntegerField") | Some("BigAutoField") => value
179 .parse::<i64>()
180 .map(super::query::FilterValue::Integer)
181 .map_err(|_| invalid()),
182 Some("FloatField") => value
183 .parse::<f64>()
184 .map(super::query::FilterValue::Float)
185 .map_err(|_| invalid()),
186 Some("UuidField") | Some("UUIDField") => value
187 .parse()
188 .map(super::query::FilterValue::Uuid)
189 .map_err(|_| invalid()),
190 Some("DateTimeField") => chrono::DateTime::parse_from_rfc3339(value)
191 .map(|value| super::query::FilterValue::Timestamp(value.with_timezone(&chrono::Utc)))
192 .map_err(|_| invalid()),
193 Some("DateField") => value
194 .parse()
195 .map(super::query::FilterValue::Date)
196 .map_err(|_| invalid()),
197 Some("TimeField") => value
198 .parse()
199 .map(super::query::FilterValue::Time)
200 .map_err(|_| invalid()),
201 Some("DecimalField") => value
202 .parse()
203 .map(super::query::FilterValue::Decimal)
204 .map_err(|_| invalid()),
205 _ => Ok(super::query::FilterValue::String(value.to_owned())),
206 }
207}
208
209pub trait Model: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone {
217 type PrimaryKey: Send + Sync + Clone + std::fmt::Display;
219
220 type Fields: FieldSelector;
225
226 type Objects: super::custom_manager::CustomManager<Model = Self> + Default;
233
234 fn table_name() -> &'static str;
236
237 fn new_fields() -> Self::Fields;
242
243 fn app_label() -> &'static str {
248 "default"
249 }
250
251 fn primary_key_field() -> &'static str {
253 "id"
254 }
255
256 fn primary_key_filter_value(pk: Self::PrimaryKey) -> super::query::FilterValue {
265 let value = pk.to_string();
266 let type_name = std::any::type_name::<Self::PrimaryKey>();
267
268 if [
269 std::any::type_name::<i8>(),
270 std::any::type_name::<i16>(),
271 std::any::type_name::<i32>(),
272 std::any::type_name::<i64>(),
273 std::any::type_name::<isize>(),
274 std::any::type_name::<i128>(),
275 ]
276 .contains(&type_name)
277 {
278 return value
279 .parse::<i128>()
280 .map(super::query::FilterValue::from)
281 .unwrap_or(super::query::FilterValue::String(value));
282 }
283
284 if [
285 std::any::type_name::<u8>(),
286 std::any::type_name::<u16>(),
287 std::any::type_name::<u32>(),
288 std::any::type_name::<u64>(),
289 std::any::type_name::<usize>(),
290 std::any::type_name::<u128>(),
291 ]
292 .contains(&type_name)
293 {
294 return value
295 .parse::<u128>()
296 .map(super::query::FilterValue::from)
297 .unwrap_or(super::query::FilterValue::String(value));
298 }
299
300 if type_name == std::any::type_name::<bool>() {
301 return value
302 .parse::<bool>()
303 .map(super::query::FilterValue::Boolean)
304 .unwrap_or(super::query::FilterValue::String(value));
305 }
306
307 if type_name == std::any::type_name::<f32>() {
308 return value
309 .parse::<f32>()
310 .map(|value| super::query::FilterValue::Float(f64::from(value)))
311 .unwrap_or(super::query::FilterValue::String(value));
312 }
313
314 if type_name == std::any::type_name::<f64>() {
315 return value
316 .parse::<f64>()
317 .map(super::query::FilterValue::Float)
318 .unwrap_or(super::query::FilterValue::String(value));
319 }
320
321 if matches!(
322 type_name,
323 name if name == std::any::type_name::<String>()
324 || name == std::any::type_name::<&str>()
325 || name == std::any::type_name::<std::borrow::Cow<'static, str>>()
326 ) {
327 return super::query::FilterValue::String(value);
328 }
329
330 if type_name == std::any::type_name::<uuid::Uuid>() {
331 return value
332 .parse()
333 .map(super::query::FilterValue::Uuid)
334 .unwrap_or(super::query::FilterValue::String(value));
335 }
336
337 if is_timezone_aware_datetime_type(type_name) {
338 return chrono::DateTime::parse_from_rfc3339(&value)
339 .map(|value| {
340 super::query::FilterValue::Timestamp(value.with_timezone(&chrono::Utc))
341 })
342 .unwrap_or(super::query::FilterValue::String(value));
343 }
344
345 if is_decimal_type(type_name) {
346 return value
347 .parse()
348 .map(super::query::FilterValue::Decimal)
349 .unwrap_or(super::query::FilterValue::String(value));
350 }
351
352 if type_name == std::any::type_name::<chrono::NaiveDate>() {
353 return value
354 .parse()
355 .map(super::query::FilterValue::Date)
356 .unwrap_or(super::query::FilterValue::String(value));
357 }
358
359 if type_name == std::any::type_name::<chrono::NaiveTime>() {
360 return value
361 .parse()
362 .map(super::query::FilterValue::Time)
363 .unwrap_or(super::query::FilterValue::String(value));
364 }
365
366 value
367 .parse::<i64>()
368 .map(super::query::FilterValue::Integer)
369 .unwrap_or(super::query::FilterValue::String(value))
370 }
371
372 fn primary_key_filter_value_from_str(
382 value: &str,
383 ) -> reinhardt_core::exception::Result<super::query::FilterValue> {
384 use reinhardt_core::exception::Error;
385
386 let type_name = std::any::type_name::<Self::PrimaryKey>();
387 macro_rules! parse_standard_integer {
388 ($integer:ty, $category:literal) => {
389 if type_name == std::any::type_name::<$integer>() {
390 return value
391 .parse::<$integer>()
392 .map(super::query::FilterValue::from)
393 .map_err(|_| {
394 Error::Validation(format!(
395 concat!("invalid ", $category, " primary key: {}"),
396 value
397 ))
398 });
399 }
400 };
401 }
402
403 parse_standard_integer!(i8, "integer");
404 parse_standard_integer!(i16, "integer");
405 parse_standard_integer!(i32, "integer");
406 parse_standard_integer!(i64, "integer");
407 parse_standard_integer!(isize, "integer");
408 parse_standard_integer!(i128, "integer");
409 parse_standard_integer!(u8, "unsigned integer");
410 parse_standard_integer!(u16, "unsigned integer");
411 parse_standard_integer!(u32, "unsigned integer");
412 parse_standard_integer!(u64, "unsigned integer");
413 parse_standard_integer!(usize, "unsigned integer");
414 parse_standard_integer!(u128, "unsigned integer");
415
416 if type_name == std::any::type_name::<bool>() {
417 return value
418 .parse::<bool>()
419 .map(super::query::FilterValue::Boolean)
420 .map_err(|_| Error::Validation(format!("invalid boolean primary key: {value}")));
421 }
422
423 if type_name == std::any::type_name::<f32>() {
424 return value
425 .parse::<f32>()
426 .map(|value| super::query::FilterValue::Float(f64::from(value)))
427 .map_err(|_| Error::Validation(format!("invalid float primary key: {value}")));
428 }
429
430 if type_name == std::any::type_name::<f64>() {
431 return value
432 .parse::<f64>()
433 .map(super::query::FilterValue::Float)
434 .map_err(|_| Error::Validation(format!("invalid float primary key: {value}")));
435 }
436
437 if type_name == std::any::type_name::<uuid::Uuid>() {
438 return value
439 .parse()
440 .map(super::query::FilterValue::Uuid)
441 .map_err(|_| Error::Validation(format!("invalid UUID primary key: {value}")));
442 }
443
444 if is_timezone_aware_datetime_type(type_name) {
445 return chrono::DateTime::parse_from_rfc3339(value)
446 .map(|value| {
447 super::query::FilterValue::Timestamp(value.with_timezone(&chrono::Utc))
448 })
449 .map_err(|_| Error::Validation(format!("invalid timestamp primary key: {value}")));
450 }
451
452 if is_decimal_type(type_name) {
453 return value
454 .parse()
455 .map(super::query::FilterValue::Decimal)
456 .map_err(|_| Error::Validation(format!("invalid decimal primary key: {value}")));
457 }
458
459 if type_name == std::any::type_name::<chrono::NaiveDate>() {
460 return value
461 .parse()
462 .map(super::query::FilterValue::Date)
463 .map_err(|_| Error::Validation(format!("invalid date primary key: {value}")));
464 }
465
466 if type_name == std::any::type_name::<chrono::NaiveTime>() {
467 return value
468 .parse()
469 .map(super::query::FilterValue::Time)
470 .map_err(|_| Error::Validation(format!("invalid time primary key: {value}")));
471 }
472
473 if is_decimal_type(type_name) {
474 return value
475 .parse()
476 .map(super::query::FilterValue::Decimal)
477 .map_err(|_| Error::Validation(format!("invalid decimal primary key: {value}")));
478 }
479
480 Ok(super::query::FilterValue::String(value.to_owned()))
481 }
482
483 fn primary_key(&self) -> Option<Self::PrimaryKey>;
488
489 fn set_primary_key(&mut self, value: Self::PrimaryKey);
491
492 fn composite_primary_key() -> Option<super::composite_pk::CompositePrimaryKey> {
496 None
497 }
498
499 fn get_composite_pk_values(&self) -> HashMap<String, super::composite_pk::PkValue> {
504 HashMap::new()
505 }
506
507 fn field_metadata() -> Vec<super::inspection::FieldInfo> {
534 Vec::new()
535 }
536
537 fn serialize_database_value(&self) -> Result<DatabaseValue, DatabaseSerializationError> {
539 serialize_model_database_value(self)
540 }
541
542 fn relationship_metadata() -> Vec<super::inspection::RelationInfo> {
548 Vec::new()
549 }
550
551 fn index_metadata() -> Vec<super::inspection::IndexInfo> {
557 Vec::new()
558 }
559
560 fn constraint_metadata() -> Vec<super::inspection::ConstraintInfo> {
566 Vec::new()
567 }
568
569 fn objects() -> Self::Objects
607 where
608 Self: Sized,
609 {
610 Self::Objects::default()
611 }
612
613 fn save(
658 &mut self,
659 ) -> impl std::future::Future<Output = reinhardt_core::exception::Result<()>> + Send
660 where
661 Self: Sized,
662 {
663 async move {
664 use super::events::{EventResult, get_active_registry};
665 use super::manager::get_connection;
666
667 let registry = get_active_registry();
668 let conn = get_connection().await?;
669 let manager = super::Manager::<Self>::new();
670
671 let json = serde_json::to_value(&*self)
672 .map_err(|e| reinhardt_core::exception::Error::Database(e.to_string()))?;
673
674 if self.primary_key().is_none() {
675 let instance_id = format!("{}-new-{}", Self::table_name(), uuid::Uuid::now_v7());
677
678 if let Some(ref reg) = registry {
680 let result = reg
681 .dispatch_before_insert(Self::table_name(), &instance_id, &json)
682 .await;
683 if result == EventResult::Veto {
684 return Err(reinhardt_core::exception::Error::Database(
685 "Insert operation vetoed by event listener".to_string(),
686 ));
687 }
688 }
689
690 let created = manager.create_with_conn(&conn, self).await?;
692 *self = created;
693
694 if let Some(ref reg) = registry {
696 let final_id = format!(
697 "{}-{}",
698 Self::table_name(),
699 self.primary_key()
700 .map(|pk| pk.to_string())
701 .unwrap_or_default()
702 );
703 reg.dispatch_after_insert(Self::table_name(), &final_id)
704 .await;
705 }
706 } else {
707 let instance_id = format!(
709 "{}-{}",
710 Self::table_name(),
711 self.primary_key()
712 .map(|pk| pk.to_string())
713 .unwrap_or_default()
714 );
715
716 if let Some(ref reg) = registry {
718 let result = reg
719 .dispatch_before_update(Self::table_name(), &instance_id, &json)
720 .await;
721 if result == EventResult::Veto {
722 return Err(reinhardt_core::exception::Error::Database(
723 "Update operation vetoed by event listener".to_string(),
724 ));
725 }
726 }
727
728 let updated = manager.update_with_conn(&conn, self).await?;
730 *self = updated;
731
732 if let Some(ref reg) = registry {
734 reg.dispatch_after_update(Self::table_name(), &instance_id)
735 .await;
736 }
737 }
738
739 Ok(())
740 }
741 }
742
743 fn delete(
782 &self,
783 ) -> impl std::future::Future<Output = reinhardt_core::exception::Result<()>> + Send
784 where
785 Self: Sized,
786 {
787 async move {
788 use super::events::{EventResult, get_active_registry};
789 use super::manager::get_connection;
790
791 let pk = self.primary_key().ok_or_else(|| {
792 reinhardt_core::exception::Error::Database(
793 "Cannot delete model without primary key".to_string(),
794 )
795 })?;
796
797 let conn = get_connection().await?;
798 let manager = super::Manager::<Self>::new();
799
800 let instance_id = format!("{}-{}", Self::table_name(), pk);
801
802 if let Some(registry) = get_active_registry() {
804 let result = registry
805 .dispatch_before_delete(Self::table_name(), &instance_id)
806 .await;
807 if result == EventResult::Veto {
808 return Err(reinhardt_core::exception::Error::Database(
809 "Delete operation vetoed by event listener".to_string(),
810 ));
811 }
812 }
813
814 manager.delete_with_conn(&conn, pk.clone()).await?;
816
817 if let Some(registry) = get_active_registry() {
819 registry
820 .dispatch_after_delete(Self::table_name(), &instance_id)
821 .await;
822 }
823
824 Ok(())
825 }
826 }
827}
828
829pub trait Timestamped {
832 fn created_at(&self) -> chrono::DateTime<chrono::Utc>;
834 fn updated_at(&self) -> chrono::DateTime<chrono::Utc>;
836 fn set_updated_at(&mut self, time: chrono::DateTime<chrono::Utc>);
838}
839
840pub trait SoftDeletable {
843 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>>;
845 fn set_deleted_at(&mut self, time: Option<chrono::DateTime<chrono::Utc>>);
847 fn is_deleted(&self) -> bool {
849 self.deleted_at().is_some()
850 }
851}
852
853#[derive(Debug, Clone, Serialize, Deserialize)]
855pub struct Timestamps {
856 pub created_at: chrono::DateTime<chrono::Utc>,
858 pub updated_at: chrono::DateTime<chrono::Utc>,
860}
861
862impl Timestamps {
863 pub fn now() -> Self {
875 let now = chrono::Utc::now();
876 Self {
877 created_at: now,
878 updated_at: now,
879 }
880 }
881 pub fn touch(&mut self) {
899 self.updated_at = chrono::Utc::now();
900 }
901}
902
903#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct SoftDelete {
906 pub deleted_at: Option<chrono::DateTime<chrono::Utc>>,
908}
909
910impl SoftDelete {
911 pub fn new() -> Self {
922 Self { deleted_at: None }
923 }
924 pub fn delete(&mut self) {
939 self.deleted_at = Some(chrono::Utc::now());
940 }
941 pub fn restore(&mut self) {
957 self.deleted_at = None;
958 }
959
960 pub fn is_deleted(&self) -> bool {
962 self.deleted_at.is_some()
963 }
964}
965
966impl Default for SoftDelete {
967 fn default() -> Self {
968 Self::new()
969 }
970}
971
972#[cfg(test)]
973mod tests {
974 use super::{FieldSelector, Model};
975 use crate::orm::{Manager, query::FilterValue};
976 use serde::{Deserialize, Serialize};
977
978 #[test]
979 fn serialize_nullable_json_array_preserves_sql_null_elements() {
980 let values = vec![
981 Some(serde_json::json!({"status": "ready"})),
982 None,
983 Some(serde_json::Value::Null),
984 ];
985
986 let serialized = super::serialize_nullable_json_array(&values);
987
988 assert_eq!(
989 serialized[0],
990 serde_json::json!({"__reinhardt_json_array_element": {"status": "ready"}})
991 );
992 assert!(super::is_sql_null_array_element(&serialized[1]));
993 assert_eq!(
994 serialized[2],
995 serde_json::json!({"__reinhardt_json_array_element": null})
996 );
997 }
998
999 #[test]
1000 fn serialize_nullable_json_array_escapes_sql_null_marker_values() {
1001 let marker = serde_json::json!({"__reinhardt_sql_null_array_element": true});
1002 let serialized = super::serialize_nullable_json_array(&[Some(marker.clone())]);
1003
1004 assert!(!super::is_sql_null_array_element(&serialized[0]));
1005 assert_eq!(
1006 super::unwrap_json_array_element(&serialized[0]),
1007 Some(&marker)
1008 );
1009 }
1010
1011 #[derive(Clone, Serialize, Deserialize)]
1012 struct StringPrimaryKeyModel {
1013 id: String,
1014 }
1015
1016 #[derive(Clone, Serialize, Deserialize)]
1017 struct IntegerPrimaryKeyModel {
1018 id: i64,
1019 }
1020
1021 #[derive(Clone, Serialize, Deserialize)]
1022 struct SmallIntegerPrimaryKeyModel {
1023 id: i8,
1024 }
1025
1026 #[derive(Clone, Serialize, Deserialize)]
1027 struct DecimalPrimaryKeyModel {
1028 id: rust_decimal::Decimal,
1029 }
1030
1031 type UuidPrimaryKey = uuid::Uuid;
1032 type TimestampPrimaryKey = chrono::DateTime<chrono::Utc>;
1033 type FixedOffsetTimestampPrimaryKey = chrono::DateTime<chrono::FixedOffset>;
1034 type LocalTimestampPrimaryKey = chrono::DateTime<chrono::Local>;
1035 type DatePrimaryKey = chrono::NaiveDate;
1036 type TimePrimaryKey = chrono::NaiveTime;
1037
1038 #[derive(Clone, Serialize, Deserialize)]
1039 struct UuidPrimaryKeyModel {
1040 id: UuidPrimaryKey,
1041 }
1042
1043 #[derive(Clone, Serialize, Deserialize)]
1044 struct TimestampPrimaryKeyModel {
1045 id: TimestampPrimaryKey,
1046 }
1047
1048 #[derive(Clone, Serialize, Deserialize)]
1049 struct FixedOffsetTimestampPrimaryKeyModel {
1050 id: FixedOffsetTimestampPrimaryKey,
1051 }
1052
1053 #[derive(Clone, Serialize, Deserialize)]
1054 struct LocalTimestampPrimaryKeyModel {
1055 id: LocalTimestampPrimaryKey,
1056 }
1057
1058 #[derive(Clone, Serialize, Deserialize)]
1059 struct DatePrimaryKeyModel {
1060 id: DatePrimaryKey,
1061 }
1062
1063 #[derive(Clone, Serialize, Deserialize)]
1064 struct TimePrimaryKeyModel {
1065 id: TimePrimaryKey,
1066 }
1067
1068 #[derive(Clone)]
1069 struct PrimaryKeyTestFields;
1070
1071 impl FieldSelector for PrimaryKeyTestFields {
1072 fn with_alias(self, _alias: &str) -> Self {
1073 self
1074 }
1075 }
1076
1077 macro_rules! impl_primary_key_test_model {
1078 ($model:ty, $pk:ty) => {
1079 impl Model for $model {
1080 type PrimaryKey = $pk;
1081 type Fields = PrimaryKeyTestFields;
1082 type Objects = Manager<Self>;
1083
1084 fn table_name() -> &'static str {
1085 "primary_key_test"
1086 }
1087
1088 fn new_fields() -> Self::Fields {
1089 PrimaryKeyTestFields
1090 }
1091
1092 fn primary_key(&self) -> Option<Self::PrimaryKey> {
1093 Some(self.id.clone())
1094 }
1095
1096 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
1097 self.id = value;
1098 }
1099 }
1100 };
1101 }
1102
1103 impl_primary_key_test_model!(StringPrimaryKeyModel, String);
1104 impl_primary_key_test_model!(IntegerPrimaryKeyModel, i64);
1105 impl_primary_key_test_model!(SmallIntegerPrimaryKeyModel, i8);
1106 impl_primary_key_test_model!(DecimalPrimaryKeyModel, rust_decimal::Decimal);
1107
1108 macro_rules! impl_alias_primary_key_test_model {
1109 ($model:ty, $pk:ty) => {
1110 impl Model for $model {
1111 type PrimaryKey = $pk;
1112 type Fields = PrimaryKeyTestFields;
1113 type Objects = Manager<Self>;
1114
1115 fn table_name() -> &'static str {
1116 "primary_key_test"
1117 }
1118
1119 fn new_fields() -> Self::Fields {
1120 PrimaryKeyTestFields
1121 }
1122
1123 fn primary_key(&self) -> Option<Self::PrimaryKey> {
1124 Some(self.id)
1125 }
1126
1127 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
1128 self.id = value;
1129 }
1130
1131 fn primary_key_filter_value_from_str(
1132 value: &str,
1133 ) -> reinhardt_core::exception::Result<FilterValue> {
1134 let filter_value = super::deserialize_primary_key_filter_value_from_str::<
1135 Self::PrimaryKey,
1136 >(value)
1137 .map_err(|_| {
1138 reinhardt_core::exception::Error::Validation(format!(
1139 "invalid primary key: {value}"
1140 ))
1141 })?;
1142 if let Some(filter_value) = filter_value {
1143 return Ok(filter_value);
1144 }
1145 let primary_key =
1146 super::deserialize_primary_key_from_str::<Self::PrimaryKey>(value)
1147 .map_err(|_| {
1148 reinhardt_core::exception::Error::Validation(format!(
1149 "invalid primary key: {value}"
1150 ))
1151 })?;
1152 Ok(Self::primary_key_filter_value(primary_key))
1153 }
1154 }
1155 };
1156 }
1157
1158 impl_alias_primary_key_test_model!(UuidPrimaryKeyModel, UuidPrimaryKey);
1159 impl_alias_primary_key_test_model!(TimestampPrimaryKeyModel, TimestampPrimaryKey);
1160 impl_alias_primary_key_test_model!(
1161 FixedOffsetTimestampPrimaryKeyModel,
1162 FixedOffsetTimestampPrimaryKey
1163 );
1164 impl_alias_primary_key_test_model!(LocalTimestampPrimaryKeyModel, LocalTimestampPrimaryKey);
1165 impl_alias_primary_key_test_model!(DatePrimaryKeyModel, DatePrimaryKey);
1166 impl_alias_primary_key_test_model!(TimePrimaryKeyModel, TimePrimaryKey);
1167
1168 #[rstest::rstest]
1169 fn primary_key_filter_value_from_str_parses_date_and_time_keys() {
1170 let date = DatePrimaryKeyModel::primary_key_filter_value_from_str("2026-08-20").unwrap();
1171 let time = TimePrimaryKeyModel::primary_key_filter_value_from_str("12:34:56").unwrap();
1172 let direct_date = DatePrimaryKeyModel::primary_key_filter_value(
1173 chrono::NaiveDate::from_ymd_opt(2026, 8, 20).expect("date should be valid"),
1174 );
1175 let direct_time = TimePrimaryKeyModel::primary_key_filter_value(
1176 chrono::NaiveTime::from_hms_opt(12, 34, 56).expect("time should be valid"),
1177 );
1178
1179 let FilterValue::Date(date) = date else {
1180 panic!("date primary key should use the date filter variant");
1181 };
1182 let FilterValue::Time(time) = time else {
1183 panic!("time primary key should use the time filter variant");
1184 };
1185 let FilterValue::Date(direct_date) = direct_date else {
1186 panic!("direct date primary key should use the date filter variant");
1187 };
1188 let FilterValue::Time(direct_time) = direct_time else {
1189 panic!("direct time primary key should use the time filter variant");
1190 };
1191 assert_eq!(
1192 date,
1193 chrono::NaiveDate::from_ymd_opt(2026, 8, 20).expect("date should be valid")
1194 );
1195 assert_eq!(
1196 direct_date,
1197 chrono::NaiveDate::from_ymd_opt(2026, 8, 20).expect("date should be valid")
1198 );
1199 assert_eq!(
1200 time,
1201 chrono::NaiveTime::from_hms_opt(12, 34, 56).expect("time should be valid")
1202 );
1203 assert_eq!(
1204 direct_time,
1205 chrono::NaiveTime::from_hms_opt(12, 34, 56).expect("time should be valid")
1206 );
1207 }
1208
1209 #[test]
1210 fn primary_key_filter_value_from_str_preserves_numeric_strings() {
1211 let value = StringPrimaryKeyModel::primary_key_filter_value_from_str("00123").unwrap();
1212 assert!(matches!(value, FilterValue::String(ref value) if value == "00123"));
1213 }
1214
1215 #[test]
1216 fn primary_key_filter_value_from_str_parses_integer_keys() {
1217 let value = IntegerPrimaryKeyModel::primary_key_filter_value_from_str("42").unwrap();
1218 assert!(matches!(value, FilterValue::Integer(42)));
1219 }
1220
1221 #[test]
1222 fn primary_key_filter_value_from_str_rejects_invalid_integer_keys() {
1223 let error = IntegerPrimaryKeyModel::primary_key_filter_value_from_str("not-an-integer")
1224 .unwrap_err();
1225 assert!(matches!(
1226 error,
1227 reinhardt_core::exception::Error::Validation(_)
1228 ));
1229 }
1230
1231 #[test]
1232 fn primary_key_filter_value_from_str_rejects_out_of_range_integer_keys() {
1233 let error =
1234 SmallIntegerPrimaryKeyModel::primary_key_filter_value_from_str("128").unwrap_err();
1235 assert!(matches!(
1236 error,
1237 reinhardt_core::exception::Error::Validation(_)
1238 ));
1239 }
1240
1241 #[test]
1242 fn primary_key_filter_value_from_str_parses_decimal_keys() {
1243 let value = DecimalPrimaryKeyModel::primary_key_filter_value_from_str("1.25").unwrap();
1244 assert!(matches!(
1245 value,
1246 FilterValue::Decimal(value) if value == rust_decimal::Decimal::new(125, 2)
1247 ));
1248 }
1249
1250 #[test]
1251 fn primary_key_filter_value_from_str_uses_uuid_filter_for_aliases() {
1252 let value = UuidPrimaryKeyModel::primary_key_filter_value_from_str(
1253 "018e9c80-0b25-7d44-9c68-3a88f6797553",
1254 )
1255 .unwrap();
1256 assert!(matches!(value, FilterValue::Uuid(_)));
1257 }
1258
1259 #[test]
1260 fn primary_key_filter_value_from_str_uses_timestamp_filter_for_aliases() {
1261 for value in [
1262 TimestampPrimaryKeyModel::primary_key_filter_value_from_str("2026-08-19T00:00:00Z")
1263 .unwrap(),
1264 FixedOffsetTimestampPrimaryKeyModel::primary_key_filter_value_from_str(
1265 "2026-08-19T00:00:00+09:00",
1266 )
1267 .unwrap(),
1268 LocalTimestampPrimaryKeyModel::primary_key_filter_value_from_str(
1269 "2026-08-19T00:00:00Z",
1270 )
1271 .unwrap(),
1272 ] {
1273 assert!(matches!(value, FilterValue::Timestamp(_)));
1274 }
1275 }
1276}