laterite_admin/
persist.rs1use std::collections::HashMap;
11use std::sync::Arc;
12
13use laterite_core::query::{bind_values, build as to_sql, insert_returning_id, text_cast};
14use laterite_core::strata::async_trait;
15use laterite_core::validation::ErrorBag;
16use laterite_core::Db;
17use sea_query::{Alias, Expr, Query, SimpleExpr};
18
19use crate::form::FormConfig;
20
21pub enum SaveError {
23 Invalid(ErrorBag),
26 Failed(String),
29}
30
31#[async_trait]
35pub trait Persister: Send + Sync + 'static {
36 async fn create(&self, db: &Db, data: &HashMap<String, String>) -> Result<i64, SaveError>;
38 async fn update(
40 &self,
41 db: &Db,
42 id: &str,
43 data: &HashMap<String, String>,
44 ) -> Result<(), SaveError>;
45}
46
47pub struct PersisterReg {
49 pub name: String,
50 pub persister: Arc<dyn Persister>,
51}
52
53impl PersisterReg {
54 pub fn new(name: impl Into<String>, persister: Arc<dyn Persister>) -> Self {
55 Self {
56 name: name.into(),
57 persister,
58 }
59 }
60}
61
62pub type PersisterRegistry = HashMap<String, Arc<dyn Persister>>;
64
65pub(crate) struct DefaultPersister {
69 entity: String,
70 id_field: String,
71 columns: Vec<String>,
72}
73
74impl DefaultPersister {
75 pub(crate) fn from_config(config: &FormConfig) -> Self {
76 Self {
77 entity: config.entity.clone(),
78 id_field: config.id_field.clone(),
79 columns: config.fields.iter().map(|f| f.name.clone()).collect(),
80 }
81 }
82}
83
84#[async_trait]
85impl Persister for DefaultPersister {
86 async fn create(&self, db: &Db, data: &HashMap<String, String>) -> Result<i64, SaveError> {
87 let stmt = {
91 let vals: Vec<SimpleExpr> = self
92 .columns
93 .iter()
94 .map(|c| {
95 let v: Option<String> = data.get(c).filter(|s| !s.is_empty()).cloned();
98 v.into()
99 })
100 .collect();
101 Query::insert()
102 .into_table(Alias::new(&self.entity))
103 .columns(self.columns.iter().map(Alias::new))
104 .values_panic(vals)
105 .to_owned()
106 };
107 insert_returning_id(db, stmt, Alias::new(&self.id_field))
108 .await
109 .map_err(|e| SaveError::Failed(e.to_string()))
110 }
111
112 async fn update(
113 &self,
114 db: &Db,
115 id: &str,
116 data: &HashMap<String, String>,
117 ) -> Result<(), SaveError> {
118 let (sql, values) = {
119 let mut update = Query::update();
120 update.table(Alias::new(&self.entity));
121 for column in &self.columns {
122 let v: Option<String> = data.get(column).filter(|s| !s.is_empty()).cloned();
125 update.value(Alias::new(column), v);
126 }
127 update.and_where(
128 Expr::col(Alias::new(&self.id_field))
129 .cast_as(Alias::new(text_cast(db.backend)))
130 .eq(id),
131 );
132 to_sql(db.backend, update)
133 };
134 bind_values(sqlx::query(&sql), values)
135 .execute(&db.pool)
136 .await
137 .map(|_| ())
138 .map_err(|e| SaveError::Failed(e.to_string()))
139 }
140}