wb_cache/test/simulation/db/entity/
customer.rs

1use async_trait::async_trait;
2use fieldx_plus::fx_plus;
3use sea_orm::entity::prelude::*;
4use sea_orm::Condition;
5use sea_orm::DeleteMany;
6use sea_orm::IntoActiveModel;
7use sea_orm::QuerySelect;
8use serde::Deserialize;
9use serde::Serialize;
10
11use std::fmt::Debug;
12use std::fmt::Display;
13use std::sync::Arc;
14
15use crate::test::simulation::db::cache::CacheUpdates;
16use crate::test::simulation::db::cache::DBProvider;
17use crate::test::simulation::db::cache::DCCommon;
18use crate::test::simulation::types::Result;
19use crate::test::simulation::types::SimErrorAny;
20use crate::types::DataControllerResponse;
21use crate::update_iterator::UpdateIterator;
22use crate::DataController;
23
24#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
25#[sea_orm(table_name = "customers")]
26#[serde(deny_unknown_fields)]
27pub struct Model {
28    #[sea_orm(primary_key)]
29    #[serde(rename = "i")]
30    pub id:            i32,
31    #[sea_orm(unique, indexed)]
32    #[serde(rename = "e")]
33    pub email:         String,
34    #[serde(rename = "f")]
35    pub first_name:    String,
36    #[serde(rename = "l")]
37    pub last_name:     String,
38    /// The simulation day number when the user was registered.
39    #[serde(rename = "d")]
40    pub registered_on: i32,
41}
42
43#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
44pub enum Relation {}
45
46impl ActiveModelBehavior for ActiveModel {}
47
48/// The cache key type for customer records.
49#[derive(Clone, Debug, PartialEq, Eq, Hash)]
50pub enum CustomerBy {
51    /// The primary key
52    Id(i32),
53    /// The secondary key
54    Email(String),
55}
56
57impl Display for CustomerBy {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            CustomerBy::Id(id) => write!(f, "#{id}"),
61            CustomerBy::Email(email) => write!(f, "{email}"),
62        }
63    }
64}
65
66/// The manager and data controller for customer records in the database.
67#[fx_plus(
68    child(DBCP, unwrap(or_else(SimErrorAny, super::dbcp_gone("customer manager")))),
69    sync,
70    rc
71)]
72pub struct Manager<DBCP>
73where
74    DBCP: DBProvider, {}
75
76impl<DBCP> Manager<DBCP>
77where
78    DBCP: DBProvider,
79{
80    /// Fetch customer record from the database by its ID key.
81    pub async fn get_by_id(&self, id: i32) -> Result<Option<Model>> {
82        let parent = self.parent()?;
83        let db = parent.db_connection()?;
84        Ok(Entity::find_by_id(id).one(&db).await?)
85    }
86
87    /// Fetch customer record from the database by its email key.
88    pub async fn get_by_email(&self, email: &str) -> Result<Option<Model>> {
89        let parent = self.parent()?;
90        let db = parent.db_connection()?;
91        Ok(Entity::find().filter(Column::Email.eq(email)).one(&db).await?)
92    }
93}
94
95impl<DBCP> Debug for Manager<DBCP>
96where
97    DBCP: DBProvider,
98{
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(f, "CustomerManager")
101    }
102}
103
104#[async_trait]
105impl<DBCP> DataController for Manager<DBCP>
106where
107    DBCP: DBProvider,
108{
109    type CacheUpdate = CacheUpdates<ActiveModel>;
110    type Error = SimErrorAny;
111    type Key = CustomerBy;
112    type Value = Model;
113
114    async fn get_for_key(&self, key: &Self::Key) -> Result<Option<Self::Value>> {
115        Ok(match key {
116            CustomerBy::Id(id) => self.get_by_id(*id).await?,
117            CustomerBy::Email(email) => self.get_by_email(email).await?,
118        })
119    }
120
121    async fn get_primary_key_for(&self, key: &Self::Key) -> Result<Option<Self::Key>> {
122        Ok(match key {
123            CustomerBy::Id(_) => Some(key.clone()),
124            // Select the ID alone. Or fail...
125            CustomerBy::Email(email) => Entity::find()
126                .filter(Column::Email.eq(email))
127                .select_only()
128                .column(Column::Id)
129                .into_tuple::<i32>()
130                .one(&self.parent()?.db_connection()?)
131                .await?
132                .map(CustomerBy::Id),
133        })
134    }
135
136    fn primary_key_of(&self, value: &Self::Value) -> Self::Key {
137        CustomerBy::Id(value.id)
138    }
139
140    fn secondary_keys_of(&self, value: &Self::Value) -> Vec<Self::Key> {
141        vec![CustomerBy::Email(value.email.clone())]
142    }
143
144    /// Returns true if the key variant is [`CustomerBy::Id`].
145    fn is_primary(&self, key: &Self::Key) -> bool {
146        matches!(key, CustomerBy::Id(_))
147    }
148
149    async fn write_back(&self, update_records: Arc<UpdateIterator<Self>>) -> Result<()> {
150        self.wbdc_write_back(update_records).await
151    }
152
153    async fn on_new(&self, key: &Self::Key, value: &Self::Value) -> Result<DataControllerResponse<Self>, Self::Error> {
154        self.wbdbc_on_new(key, &value.clone().into_active_model()).await
155    }
156
157    async fn on_delete(
158        &self,
159        key: &Self::Key,
160        update: Option<&CacheUpdates<ActiveModel>>,
161    ) -> Result<DataControllerResponse<Self>> {
162        self.wbdc_on_delete(key, update).await
163    }
164
165    async fn on_change(
166        &self,
167        key: &Self::Key,
168        value: &Self::Value,
169        old_value: Self::Value,
170        prev_update: Option<Self::CacheUpdate>,
171    ) -> Result<DataControllerResponse<Self>> {
172        self.wbdc_on_change(key, value, old_value, prev_update).await
173    }
174}
175
176#[async_trait]
177impl<DBCP> DCCommon<Entity, DBCP> for Manager<DBCP>
178where
179    DBCP: DBProvider,
180{
181    /// Provide correct condition for SeaORM's [`DeleteMany`] operation.
182    /// A customer entry can be deleted by either its ID or email, so we build a condition that checks both.
183    fn delete_many_condition(dm: DeleteMany<Entity>, keys: Vec<Self::Key>) -> DeleteMany<Entity> {
184        let mut by_id = vec![];
185        let mut by_email = vec![];
186        for key in keys {
187            match key {
188                CustomerBy::Id(id) => by_id.push(id),
189                CustomerBy::Email(email) => by_email.push(email),
190            }
191        }
192
193        let mut condition = Condition::any();
194
195        if !by_id.is_empty() {
196            condition = condition.add(Column::Id.is_in(by_id));
197        }
198        if !by_email.is_empty() {
199            condition = condition.add(Column::Email.is_in(by_email));
200        }
201
202        dm.filter(condition)
203    }
204}