use std::collections::HashMap;
use std::sync::Arc;
use laterite_core::query::{bind_values, build as to_sql, insert_returning_id, text_cast};
use laterite_core::strata::async_trait;
use laterite_core::validation::ErrorBag;
use laterite_core::Db;
use sea_query::{Alias, Expr, Query, SimpleExpr};
use crate::form::FormConfig;
pub enum SaveError {
Invalid(ErrorBag),
Failed(String),
}
#[async_trait]
pub trait Persister: Send + Sync + 'static {
async fn create(&self, db: &Db, data: &HashMap<String, String>) -> Result<i64, SaveError>;
async fn update(
&self,
db: &Db,
id: &str,
data: &HashMap<String, String>,
) -> Result<(), SaveError>;
}
pub struct PersisterReg {
pub name: String,
pub persister: Arc<dyn Persister>,
}
impl PersisterReg {
pub fn new(name: impl Into<String>, persister: Arc<dyn Persister>) -> Self {
Self {
name: name.into(),
persister,
}
}
}
pub type PersisterRegistry = HashMap<String, Arc<dyn Persister>>;
pub(crate) struct DefaultPersister {
entity: String,
id_field: String,
columns: Vec<String>,
}
impl DefaultPersister {
pub(crate) fn from_config(config: &FormConfig) -> Self {
Self {
entity: config.entity.clone(),
id_field: config.id_field.clone(),
columns: config.fields.iter().map(|f| f.name.clone()).collect(),
}
}
}
#[async_trait]
impl Persister for DefaultPersister {
async fn create(&self, db: &Db, data: &HashMap<String, String>) -> Result<i64, SaveError> {
let stmt = {
let vals: Vec<SimpleExpr> = self
.columns
.iter()
.map(|c| {
let v: Option<String> = data.get(c).filter(|s| !s.is_empty()).cloned();
v.into()
})
.collect();
Query::insert()
.into_table(Alias::new(&self.entity))
.columns(self.columns.iter().map(Alias::new))
.values_panic(vals)
.to_owned()
};
insert_returning_id(db, stmt, Alias::new(&self.id_field))
.await
.map_err(|e| SaveError::Failed(e.to_string()))
}
async fn update(
&self,
db: &Db,
id: &str,
data: &HashMap<String, String>,
) -> Result<(), SaveError> {
let (sql, values) = {
let mut update = Query::update();
update.table(Alias::new(&self.entity));
for column in &self.columns {
let v: Option<String> = data.get(column).filter(|s| !s.is_empty()).cloned();
update.value(Alias::new(column), v);
}
update.and_where(
Expr::col(Alias::new(&self.id_field))
.cast_as(Alias::new(text_cast(db.backend)))
.eq(id),
);
to_sql(db.backend, update)
};
bind_values(sqlx::query(&sql), values)
.execute(&db.pool)
.await
.map(|_| ())
.map_err(|e| SaveError::Failed(e.to_string()))
}
}