use crate::Result;
use crate::field::{FieldInfo, InheritanceInfo};
use crate::relationship::RelationshipInfo;
use crate::row::Row;
use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExtraFieldsBehavior {
#[default]
Ignore,
Forbid,
Allow,
}
#[derive(Debug, Clone, Default)]
pub struct ModelConfig {
pub table: bool,
pub from_attributes: bool,
pub validate_assignment: bool,
pub extra: ExtraFieldsBehavior,
pub strict: bool,
pub populate_by_name: bool,
pub use_enum_values: bool,
pub arbitrary_types_allowed: bool,
pub defer_build: bool,
pub revalidate_instances: bool,
pub json_schema_extra: Option<&'static str>,
pub title: Option<&'static str>,
}
impl ModelConfig {
pub const fn new() -> Self {
Self {
table: false,
from_attributes: false,
validate_assignment: false,
extra: ExtraFieldsBehavior::Ignore,
strict: false,
populate_by_name: false,
use_enum_values: false,
arbitrary_types_allowed: false,
defer_build: false,
revalidate_instances: false,
json_schema_extra: None,
title: None,
}
}
pub const fn table() -> Self {
Self {
table: true,
from_attributes: false,
validate_assignment: false,
extra: ExtraFieldsBehavior::Ignore,
strict: false,
populate_by_name: false,
use_enum_values: false,
arbitrary_types_allowed: false,
defer_build: false,
revalidate_instances: false,
json_schema_extra: None,
title: None,
}
}
}
pub trait Model: Sized + Send + Sync {
const TABLE_NAME: &'static str;
const PRIMARY_KEY: &'static [&'static str];
const RELATIONSHIPS: &'static [RelationshipInfo] = &[];
fn inheritance() -> InheritanceInfo {
InheritanceInfo::none()
}
fn fields() -> &'static [FieldInfo];
fn to_row(&self) -> Vec<(&'static str, Value)>;
#[allow(clippy::result_large_err)]
fn from_row(row: &Row) -> Result<Self>;
#[must_use]
fn joined_parent_row(&self) -> Option<Vec<(&'static str, Value)>> {
None
}
fn primary_key_value(&self) -> Vec<Value>;
fn is_new(&self) -> bool;
fn model_config() -> ModelConfig {
ModelConfig::new()
}
const SHARD_KEY: Option<&'static str> = None;
fn shard_key_value(&self) -> Option<Value> {
None
}
}
pub trait AutoIncrement: Model {
fn set_id(&mut self, id: i64);
}
pub trait Timestamps: Model {
fn set_created_at(&mut self, timestamp: i64);
fn set_updated_at(&mut self, timestamp: i64);
}
pub trait SoftDelete: Model {
fn mark_deleted(&mut self);
fn is_deleted(&self) -> bool;
}
pub trait ModelEvents: Model {
#[allow(unused_variables, clippy::result_large_err)]
fn before_insert(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn after_insert(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn before_update(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn after_update(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn before_delete(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn after_delete(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn on_load(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn on_refresh(&mut self) -> Result<()> {
Ok(())
}
#[allow(unused_variables, clippy::result_large_err)]
fn on_attribute_change(&mut self, changes: &[AttributeChange]) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AttributeChange {
pub field_name: &'static str,
pub old_value: serde_json::Value,
pub new_value: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FieldInfo, Row, SqlType, Value};
#[derive(Debug)]
struct TestModel;
impl Model for TestModel {
const TABLE_NAME: &'static str = "test_models";
const PRIMARY_KEY: &'static [&'static str] = &["id"];
fn fields() -> &'static [FieldInfo] {
static FIELDS: &[FieldInfo] =
&[FieldInfo::new("id", "id", SqlType::Integer).primary_key(true)];
FIELDS
}
fn to_row(&self) -> Vec<(&'static str, Value)> {
vec![]
}
fn from_row(_row: &Row) -> Result<Self> {
Ok(Self)
}
fn primary_key_value(&self) -> Vec<Value> {
vec![Value::from(1_i64)]
}
fn is_new(&self) -> bool {
false
}
}
#[test]
fn test_default_relationships_is_empty() {
assert!(TestModel::RELATIONSHIPS.is_empty());
}
impl ModelEvents for TestModel {}
#[test]
fn test_model_events_default_before_insert() {
let mut model = TestModel;
assert!(model.before_insert().is_ok());
}
#[test]
fn test_model_events_default_after_insert() {
let mut model = TestModel;
assert!(model.after_insert().is_ok());
}
#[test]
fn test_model_events_default_before_update() {
let mut model = TestModel;
assert!(model.before_update().is_ok());
}
#[test]
fn test_model_events_default_after_update() {
let mut model = TestModel;
assert!(model.after_update().is_ok());
}
#[test]
fn test_model_events_default_before_delete() {
let mut model = TestModel;
assert!(model.before_delete().is_ok());
}
#[test]
fn test_model_events_default_after_delete() {
let mut model = TestModel;
assert!(model.after_delete().is_ok());
}
#[test]
fn test_model_events_default_on_load() {
let mut model = TestModel;
assert!(model.on_load().is_ok());
}
#[test]
fn test_model_events_default_on_refresh() {
let mut model = TestModel;
assert!(model.on_refresh().is_ok());
}
#[derive(Debug)]
struct TimestampedModel {
id: Option<i64>,
created_at: i64,
updated_at: i64,
}
impl Model for TimestampedModel {
const TABLE_NAME: &'static str = "timestamped_models";
const PRIMARY_KEY: &'static [&'static str] = &["id"];
fn fields() -> &'static [FieldInfo] {
static FIELDS: &[FieldInfo] =
&[FieldInfo::new("id", "id", SqlType::Integer).primary_key(true)];
FIELDS
}
fn to_row(&self) -> Vec<(&'static str, Value)> {
vec![("id", self.id.map_or(Value::Null, Value::from))]
}
fn from_row(_row: &Row) -> Result<Self> {
Ok(Self {
id: Some(1),
created_at: 0,
updated_at: 0,
})
}
fn primary_key_value(&self) -> Vec<Value> {
vec![self.id.map_or(Value::Null, Value::from)]
}
fn is_new(&self) -> bool {
self.id.is_none()
}
}
impl ModelEvents for TimestampedModel {
fn before_insert(&mut self) -> Result<()> {
self.created_at = 1000;
self.updated_at = 1000;
Ok(())
}
fn before_update(&mut self) -> Result<()> {
self.updated_at = 2000;
Ok(())
}
}
#[test]
fn test_model_events_custom_before_insert_sets_timestamps() {
let mut model = TimestampedModel {
id: None,
created_at: 0,
updated_at: 0,
};
assert_eq!(model.created_at, 0);
assert_eq!(model.updated_at, 0);
model.before_insert().unwrap();
assert_eq!(model.created_at, 1000);
assert_eq!(model.updated_at, 1000);
}
#[test]
fn test_model_events_custom_before_update_sets_timestamp() {
let mut model = TimestampedModel {
id: Some(1),
created_at: 1000,
updated_at: 1000,
};
model.before_update().unwrap();
assert_eq!(model.created_at, 1000);
assert_eq!(model.updated_at, 2000);
}
#[test]
fn test_model_events_custom_defaults_still_work() {
let mut model = TimestampedModel {
id: Some(1),
created_at: 0,
updated_at: 0,
};
assert!(model.after_insert().is_ok());
assert!(model.after_update().is_ok());
assert!(model.before_delete().is_ok());
assert!(model.after_delete().is_ok());
assert!(model.on_load().is_ok());
assert!(model.on_refresh().is_ok());
}
#[test]
fn test_model_config_new_defaults() {
let config = ModelConfig::new();
assert!(!config.table);
assert!(!config.from_attributes);
assert!(!config.validate_assignment);
assert_eq!(config.extra, ExtraFieldsBehavior::Ignore);
assert!(!config.strict);
assert!(!config.populate_by_name);
assert!(!config.use_enum_values);
assert!(!config.arbitrary_types_allowed);
assert!(!config.defer_build);
assert!(!config.revalidate_instances);
assert!(config.json_schema_extra.is_none());
assert!(config.title.is_none());
}
#[test]
fn test_model_config_table_constructor() {
let config = ModelConfig::table();
assert!(config.table);
assert!(!config.from_attributes);
}
#[test]
fn test_extra_fields_behavior_default() {
let behavior = ExtraFieldsBehavior::default();
assert_eq!(behavior, ExtraFieldsBehavior::Ignore);
}
#[test]
fn test_model_default_config() {
let config = TestModel::model_config();
assert!(!config.table);
assert!(!config.from_attributes);
}
}