use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use super::reverse::{ReverseRelationship, generate_reverse_accessor};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum CascadeAction {
#[default]
NoAction,
Restrict,
SetNull,
SetDefault,
Cascade,
}
#[derive(Debug, Clone)]
pub struct ForeignKey<T, K> {
pub field_name: String,
pub to_field: String,
pub related_name: Option<String>,
pub on_delete: CascadeAction,
pub on_update: CascadeAction,
pub null: bool,
pub db_index: bool,
pub db_constraint: Option<String>,
_phantom_t: PhantomData<T>,
_phantom_k: PhantomData<K>,
}
impl<T, K> ForeignKey<T, K> {
pub fn new(field_name: impl Into<String>) -> Self {
Self {
field_name: field_name.into(),
to_field: "id".to_string(),
related_name: None,
on_delete: CascadeAction::default(),
on_update: CascadeAction::default(),
null: false,
db_index: true,
db_constraint: None,
_phantom_t: PhantomData,
_phantom_k: PhantomData,
}
}
pub fn to_field(mut self, to_field: impl Into<String>) -> Self {
self.to_field = to_field.into();
self
}
pub fn related_name(mut self, name: impl Into<String>) -> Self {
self.related_name = Some(name.into());
self
}
pub fn on_delete(mut self, action: CascadeAction) -> Self {
self.on_delete = action;
self
}
pub fn on_update(mut self, action: CascadeAction) -> Self {
self.on_update = action;
self
}
pub fn null(mut self, null: bool) -> Self {
self.null = null;
self
}
pub fn db_index(mut self, db_index: bool) -> Self {
self.db_index = db_index;
self
}
pub fn db_constraint(mut self, name: impl Into<String>) -> Self {
self.db_constraint = Some(name.into());
self
}
pub fn field_name(&self) -> &str {
&self.field_name
}
pub fn get_to_field(&self) -> &str {
&self.to_field
}
pub fn get_related_name(&self) -> Option<&str> {
self.related_name.as_deref()
}
pub fn get_on_delete(&self) -> CascadeAction {
self.on_delete
}
pub fn get_on_update(&self) -> CascadeAction {
self.on_update
}
pub fn is_null(&self) -> bool {
self.null
}
pub fn has_db_index(&self) -> bool {
self.db_index
}
pub fn get_db_constraint(&self) -> Option<&str> {
self.db_constraint.as_deref()
}
}
impl<T, K> Default for ForeignKey<T, K> {
fn default() -> Self {
Self::new("id")
}
}
impl<T, K> ReverseRelationship for ForeignKey<T, K> {
fn get_or_generate_reverse_name(&self, model_name: &str) -> String {
self.related_name
.clone()
.unwrap_or_else(|| generate_reverse_accessor(model_name))
}
fn explicit_reverse_name(&self) -> Option<&str> {
self.related_name.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code)]
#[derive(Clone)]
struct User {
id: i64,
name: String,
}
#[test]
fn test_foreign_key_creation() {
let fk: ForeignKey<User, i64> = ForeignKey::new("author_id");
assert_eq!(fk.field_name(), "author_id");
assert_eq!(fk.get_to_field(), "id");
assert_eq!(fk.get_related_name(), None);
assert_eq!(fk.get_on_delete(), CascadeAction::NoAction);
assert_eq!(fk.get_on_update(), CascadeAction::NoAction);
assert!(!fk.is_null());
assert!(fk.has_db_index());
}
#[test]
fn test_foreign_key_builder() {
let fk: ForeignKey<User, i64> = ForeignKey::new("author_id")
.related_name("posts")
.on_delete(CascadeAction::Cascade)
.on_update(CascadeAction::SetNull)
.null(true)
.db_index(false)
.db_constraint("fk_posts_author");
assert_eq!(fk.field_name(), "author_id");
assert_eq!(fk.get_related_name(), Some("posts"));
assert_eq!(fk.get_on_delete(), CascadeAction::Cascade);
assert_eq!(fk.get_on_update(), CascadeAction::SetNull);
assert!(fk.is_null());
assert!(!fk.has_db_index());
assert_eq!(fk.get_db_constraint(), Some("fk_posts_author"));
}
#[test]
fn test_cascade_action_default() {
assert_eq!(CascadeAction::default(), CascadeAction::NoAction);
}
#[test]
fn test_cascade_actions() {
let actions = vec![
CascadeAction::NoAction,
CascadeAction::Restrict,
CascadeAction::SetNull,
CascadeAction::SetDefault,
CascadeAction::Cascade,
];
for action in actions {
let fk: ForeignKey<User, i64> = ForeignKey::new("test_id").on_delete(action);
assert_eq!(fk.get_on_delete(), action);
}
}
#[test]
fn test_to_field_customization() {
let fk: ForeignKey<User, i64> = ForeignKey::new("author_id").to_field("user_id");
assert_eq!(fk.get_to_field(), "user_id");
}
#[test]
fn test_null_configuration() {
let fk1: ForeignKey<User, i64> = ForeignKey::new("author_id").null(true);
assert!(fk1.is_null());
let fk2: ForeignKey<User, i64> = ForeignKey::new("author_id").null(false);
assert!(!fk2.is_null());
}
#[test]
fn test_db_index_configuration() {
let fk1: ForeignKey<User, i64> = ForeignKey::new("author_id").db_index(true);
assert!(fk1.has_db_index());
let fk2: ForeignKey<User, i64> = ForeignKey::new("author_id").db_index(false);
assert!(!fk2.has_db_index());
}
}