use crate::db::{DatabaseBackend, DatabaseError, Model, Result};
#[derive(Debug, Clone)]
pub enum ForeignKey<T: Model> {
PrimaryKey(T::PrimaryKey),
Model(Box<T>),
}
impl<T: Model> ForeignKey<T> {
pub fn primary_key(&self) -> &T::PrimaryKey {
match self {
Self::PrimaryKey(pk) => pk,
Self::Model(model) => model.primary_key(),
}
}
pub fn model(&self) -> Option<&T> {
match self {
Self::Model(model) => Some(model),
Self::PrimaryKey(_) => None,
}
}
#[track_caller]
pub fn unwrap(self) -> T {
match self {
Self::Model(model) => *model,
Self::PrimaryKey(_) => panic!("object has not been retrieved from the database"),
}
}
pub async fn get<DB: DatabaseBackend>(&mut self, db: &DB) -> Result<&T> {
match self {
Self::Model(model) => Ok(model),
Self::PrimaryKey(pk) => {
let model = T::get_by_primary_key(db, pk.clone())
.await?
.ok_or(DatabaseError::ForeignKeyNotFound)?;
*self = Self::Model(Box::new(model));
match self.model() {
Some(model) => Ok(model),
None => unreachable!("model was just set"),
}
}
}
}
}
impl<T: Model> PartialEq for ForeignKey<T>
where
T::PrimaryKey: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.primary_key() == other.primary_key()
}
}
impl<T: Model> Eq for ForeignKey<T> where T::PrimaryKey: Eq {}
impl<T: Model> From<T> for ForeignKey<T> {
fn from(model: T) -> Self {
Self::Model(Box::new(model))
}
}
impl<T: Model> From<&T> for ForeignKey<T> {
fn from(model: &T) -> Self {
Self::PrimaryKey(model.primary_key().clone())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
pub enum ForeignKeyOnDeletePolicy {
NoAction,
#[default]
Restrict,
Cascade,
SetNone,
}
impl From<ForeignKeyOnDeletePolicy> for sea_query::ForeignKeyAction {
fn from(value: ForeignKeyOnDeletePolicy) -> Self {
match value {
ForeignKeyOnDeletePolicy::NoAction => Self::NoAction,
ForeignKeyOnDeletePolicy::Restrict => Self::Restrict,
ForeignKeyOnDeletePolicy::Cascade => Self::Cascade,
ForeignKeyOnDeletePolicy::SetNone => Self::SetNull,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
pub enum ForeignKeyOnUpdatePolicy {
NoAction,
Restrict,
#[default]
Cascade,
SetNone,
}
impl From<ForeignKeyOnUpdatePolicy> for sea_query::ForeignKeyAction {
fn from(value: ForeignKeyOnUpdatePolicy) -> Self {
match value {
ForeignKeyOnUpdatePolicy::NoAction => Self::NoAction,
ForeignKeyOnUpdatePolicy::Restrict => Self::Restrict,
ForeignKeyOnUpdatePolicy::Cascade => Self::Cascade,
ForeignKeyOnUpdatePolicy::SetNone => Self::SetNull,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::{Auto, model};
#[derive(Debug, Clone, PartialEq)]
#[model]
struct TestModel {
#[model(primary_key)]
id: Auto<i32>,
}
#[test]
fn test_primary_key() {
let fk = ForeignKey::<TestModel>::PrimaryKey(Auto::fixed(1));
assert_eq!(fk.primary_key(), &Auto::fixed(1));
}
#[test]
fn test_model() {
let model = TestModel { id: Auto::fixed(1) };
let fk = ForeignKey::Model(Box::new(model.clone()));
assert_eq!(fk.model().unwrap(), &model);
assert_eq!(fk.primary_key(), &Auto::fixed(1));
}
#[test]
fn test_unwrap_model() {
let model = TestModel { id: Auto::fixed(1) };
let fk = ForeignKey::Model(Box::new(model.clone()));
assert_eq!(fk.unwrap(), model);
}
#[should_panic(expected = "object has not been retrieved from the database")]
#[test]
fn unwrap_primary_key() {
let fk = ForeignKey::<TestModel>::PrimaryKey(Auto::fixed(1));
fk.unwrap();
}
#[test]
fn test_partial_eq() {
let fk1 = ForeignKey::<TestModel>::PrimaryKey(Auto::fixed(1));
let fk2 = ForeignKey::<TestModel>::PrimaryKey(Auto::fixed(1));
assert_eq!(fk1, fk2);
}
#[test]
fn test_from_model() {
let model = TestModel { id: Auto::fixed(1) };
let fk: ForeignKey<TestModel> = ForeignKey::from(model.clone());
assert_eq!(fk.model().unwrap(), &model);
}
#[test]
fn test_from_model_ref() {
let model = TestModel { id: Auto::fixed(1) };
let fk: ForeignKey<TestModel> = ForeignKey::from(&model);
assert_eq!(fk.primary_key(), &Auto::fixed(1));
}
}