use super::{IntoSqlValue, ModelHelper, Schema};
use std::fmt::Display;
use zino_core::{
bail,
datetime::DateTime,
error::Error,
extension::{JsonObjectExt, JsonValueExt},
model::{ModelHooks, Mutation, Query},
validation::Validation,
warn, JsonValue, Map,
};
pub trait ModelAccessor<K>: Schema<PrimaryKey = K>
where
K: Default + Display + PartialEq,
{
fn id(&self) -> &K;
#[inline]
fn name(&self) -> &str {
""
}
#[inline]
fn namespace(&self) -> &str {
""
}
#[inline]
fn visibility(&self) -> &str {
"Private"
}
#[inline]
fn status(&self) -> &str {
"Active"
}
#[inline]
fn description(&self) -> &str {
""
}
#[inline]
fn extra(&self) -> Option<&Map> {
None
}
#[inline]
fn created_at(&self) -> DateTime {
DateTime::default()
}
#[inline]
fn updated_at(&self) -> DateTime {
DateTime::default()
}
#[inline]
fn deleted_at(&self) -> Option<DateTime> {
None
}
#[inline]
fn version(&self) -> u64 {
0
}
#[inline]
fn edition(&self) -> u32 {
0
}
fn snapshot(&self) -> Map {
let mut snapshot = Map::new();
snapshot.upsert(Self::PRIMARY_KEY_NAME, self.primary_key_value());
snapshot.upsert("name", self.name());
snapshot.upsert("status", self.status());
snapshot.upsert("updated_at", self.updated_at());
snapshot.upsert("version", self.version());
snapshot
}
#[inline]
fn has_name(&self) -> bool {
!self.name().is_empty()
}
#[inline]
fn has_namespace_prefix(&self, namespace: &str) -> bool {
self.namespace()
.strip_prefix(namespace)
.is_some_and(|s| s.is_empty() || s.starts_with(':'))
}
#[inline]
fn has_namespace_suffix(&self, namespace: &str) -> bool {
self.namespace()
.strip_suffix(namespace)
.is_some_and(|s| s.is_empty() || s.ends_with(':'))
}
#[inline]
fn has_visibility(&self, visibility: &str) -> bool {
self.visibility().eq_ignore_ascii_case(visibility)
}
#[inline]
fn is_public(&self) -> bool {
self.visibility().eq_ignore_ascii_case("Public")
}
#[inline]
fn is_internal(&self) -> bool {
self.visibility().eq_ignore_ascii_case("Internal")
}
#[inline]
fn is_protected(&self) -> bool {
self.visibility().eq_ignore_ascii_case("Protected")
}
#[inline]
fn is_private(&self) -> bool {
self.visibility().eq_ignore_ascii_case("Private")
}
#[inline]
fn has_status(&self, status: &str) -> bool {
self.status().eq_ignore_ascii_case(status)
}
#[inline]
fn is_active(&self) -> bool {
self.status().eq_ignore_ascii_case("Active")
}
#[inline]
fn is_inactive(&self) -> bool {
self.status().eq_ignore_ascii_case("Inactive")
}
#[inline]
fn is_locked(&self) -> bool {
self.status().eq_ignore_ascii_case("Locked")
}
#[inline]
fn is_deleted(&self) -> bool {
self.status().eq_ignore_ascii_case("Deleted")
}
#[inline]
fn is_archived(&self) -> bool {
self.status().eq_ignore_ascii_case("Archived")
}
#[inline]
fn has_description(&self) -> bool {
!self.description().is_empty()
}
#[inline]
fn get_extra_value(&self, key: &str) -> Option<&JsonValue> {
self.extra()?.get(key)
}
#[inline]
fn next_version(&self) -> u64 {
self.version() + 1
}
fn current_version_filters(&self) -> Map {
let mut filters = Map::new();
filters.upsert(Self::PRIMARY_KEY_NAME, self.id().to_string());
filters.upsert("version", self.version());
filters
}
fn current_version_query(&self) -> Query {
let mut query = Self::default_query();
query.append_filters(&mut self.current_version_filters());
query
}
fn next_version_filters(&self) -> Map {
let mut filters = Map::new();
filters.upsert(Self::PRIMARY_KEY_NAME, self.id().to_string());
filters.upsert("version", self.next_version());
filters
}
fn next_version_updates(&self) -> Map {
let mut updates = Map::new();
updates.upsert("updated_at", DateTime::now().into_sql_value());
updates.upsert("version", self.next_version());
updates
}
fn next_version_mutation(&self, updates: &mut Map) -> Mutation {
let mut mutation = Self::default_mutation();
mutation.append_updates(updates);
mutation.append_updates(&mut self.next_version_updates());
mutation
}
#[inline]
fn next_edition(&self) -> u32 {
self.edition() + 1
}
fn current_edition_filters(&self) -> Map {
let mut filters = Map::new();
filters.upsert(Self::PRIMARY_KEY_NAME, self.id().to_string());
filters.upsert("edition", self.edition());
filters
}
fn current_edition_query(&self) -> Query {
let mut query = Self::default_query();
query.append_filters(&mut self.current_edition_filters());
query
}
fn next_edition_filters(&self) -> Map {
let mut filters = Map::new();
filters.upsert(Self::PRIMARY_KEY_NAME, self.id().to_string());
filters.upsert("edition", self.next_edition());
filters
}
fn next_edition_updates(&self) -> Map {
let mut updates = Map::new();
updates.upsert("updated_at", DateTime::now().into_sql_value());
updates.upsert("version", self.next_version());
updates.upsert("edition", self.next_edition());
updates
}
fn next_edition_mutation(&self, updates: &mut Map) -> Mutation {
let mut mutation = Self::default_mutation();
mutation.append_updates(updates);
mutation.append_updates(&mut self.next_edition_updates());
mutation
}
fn soft_delete_mutation(&self) -> Mutation {
let mut mutation = Self::default_mutation();
let mut updates = self.next_edition_updates();
updates.upsert("status", "Deleted");
mutation.append_updates(&mut updates);
mutation
}
fn lock_mutation(&self) -> Mutation {
let mut mutation = Self::default_mutation();
let mut updates = self.next_edition_updates();
updates.upsert("status", "Locked");
mutation.append_updates(&mut updates);
mutation
}
fn archive_mutation(&self) -> Mutation {
let mut mutation = Self::default_mutation();
let mut updates = self.next_edition_updates();
updates.upsert("status", "Archived");
mutation.append_updates(&mut updates);
mutation
}
fn default_snapshot_query() -> Query {
let mut query = Query::default();
let fields = [
Self::PRIMARY_KEY_NAME,
"name",
"status",
"updated_at",
"version",
];
query.allow_fields(&fields);
query.deny_fields(Self::write_only_fields());
query
}
fn default_list_query() -> Query {
let mut query = Query::default();
let ignored_fields = [Self::write_only_fields(), &["extra"]].concat();
query.allow_fields(Self::fields());
query.deny_fields(&ignored_fields);
query.add_filter("status", Map::from_entry("$ne", "Deleted"));
query.order_desc("updated_at");
query
}
async fn check_constraints(&self) -> Result<Validation, Error> {
let mut validation = Validation::new();
if self.id() == &K::default() {
validation.record(Self::PRIMARY_KEY_NAME, "should not be a default value");
}
Ok(validation)
}
async fn fetch(query: &Query) -> Result<Vec<Map>, Error> {
let mut models = Self::find(query).await?;
let translate_enabled = query.translate_enabled();
for model in models.iter_mut() {
Self::after_decode(model).await?;
translate_enabled.then(|| Self::translate_model(model));
}
Ok(models)
}
async fn fetch_by_id(id: &K) -> Result<Map, Error> {
let mut model = Self::find_by_id::<Map>(id)
.await?
.ok_or_else(|| warn!("404 Not Found: cannot find the model `{}`", id))?;
Self::after_decode(&mut model).await?;
Self::translate_model(&mut model);
Ok(model)
}
async fn soft_delete_by_id(id: &K) -> Result<(), Error> {
let mut model = Self::try_get_model(id).await?;
let model_data = model.before_soft_delete().await?;
let query = model.current_version_query();
let mut mutation = model.soft_delete_mutation();
let ctx = Self::update_one(&query, &mut mutation).await?;
Self::after_soft_delete(&ctx, model_data).await?;
Ok(())
}
async fn lock_by_id(id: &K) -> Result<(), Error> {
let mut model = Self::try_get_model(id).await?;
let model_data = model.before_lock().await?;
let query = model.current_version_query();
let mut mutation = model.lock_mutation();
let ctx = Self::update_one(&query, &mut mutation).await?;
Self::after_lock(&ctx, model_data).await?;
Ok(())
}
async fn archive_by_id(id: &K) -> Result<(), Error> {
let mut model = Self::try_get_model(id).await?;
let model_data = model.before_archive().await?;
let query = model.current_version_query();
let mut mutation = model.archive_mutation();
let ctx = Self::update_one(&query, &mut mutation).await?;
Self::after_archive(&ctx, model_data).await?;
Ok(())
}
async fn mutate_by_id(
id: &K,
data: &mut Map,
extension: Option<<Self as ModelHooks>::Extension>,
) -> Result<(Validation, Self), Error> {
Self::before_extract().await?;
let mut model = Self::try_get_model(id).await?;
let version = model.version();
if data.get_u64("version").is_some_and(|v| version != v) {
bail!(
"409 Conflict: there is a version conflict for the model `{}`",
id
);
}
Self::before_validation(data, extension.as_ref()).await?;
let validation = model.read_map(data);
if !validation.is_success() {
return Ok((validation, model));
}
if let Some(extension) = extension {
model.after_extract(extension).await?;
}
let validation = model.check_constraints().await?;
if !validation.is_success() {
return Ok((validation, model));
}
if model.is_deleted() {
data.retain(|key, _value| key == "status");
} else if model.is_locked() {
data.retain(|key, _value| key == "visibility" || key == "status");
} else if model.is_archived() {
bail!("403 Forbidden: archived model `{}` can not be modified", id);
}
model.after_validation(data).await?;
let query = model.current_version_query();
let mut mutation = model.next_version_mutation(data);
let model_data = model.before_update().await?;
let ctx = Self::update_one(&query, &mut mutation).await?;
if ctx.rows_affected() != Some(1) {
bail!(
"404 Not Found: there is no version `{}` for the model `{}`",
version,
id,
);
}
Self::after_update(&ctx, model_data).await?;
Ok((validation, model))
}
async fn random_associations() -> Result<Map, Error> {
let mut associations = Map::new();
let table_name = Self::table_name();
for col in Self::columns() {
if col.reference().is_some_and(|r| r.name() == table_name) {
let col_name = col.name();
let size = col.random_size();
let values = Self::sample(size).await?;
if col.is_array_type() {
associations.upsert(col_name, values);
} else {
associations.upsert(col_name, values.first().cloned());
}
}
}
Ok(associations)
}
async fn mock() -> Result<(Validation, Self), Error> {
let mut data = Self::before_mock().await?;
let mut associations = Self::random_associations().await?;
data.append(&mut associations);
for col in Self::columns() {
if !col.has_attribute("constructor") {
let value = col.mock_value();
if !value.is_ignorable() {
data.upsert(col.name(), value);
}
}
}
Self::before_validation(&mut data, None).await?;
let mut model = Self::new();
let validation = model.read_map(&data);
if !validation.is_success() {
return Ok((validation, model));
}
let validation = model.check_constraints().await?;
if !validation.is_success() {
return Ok((validation, model));
}
model.after_validation(&mut data).await?;
model.after_mock().await?;
Ok((validation, model))
}
}