Skip to main content

siderite_orm/
relations.rs

1//! Relation field types.
2//!
3//! * [`ForeignKey<T>`]: a column holding `T`'s primary key, plus an optional
4//!   cached `T` filled by `select_related` / `prefetch_related` or a previous
5//!   fetch. `Option<ForeignKey<T>>` is a nullable FK.
6//! * [`OneToOne<T>`]: the same storage; the derive marks the column unique.
7//! * Many-to-many relations are declared on the model
8//!   (`#[model(many_to_many(tags(Tag)))]`), not as struct fields, because they
9//!   have no column and no serialized form.
10
11use crate::db::Db;
12use crate::error::OrmError;
13use crate::model::{ManyToManyMeta, Model};
14use crate::types::{DbType, SqlType};
15use crate::value::Value;
16use serde::{Deserialize, Deserializer, Serialize, Serializer};
17use serde_json::Value as JsonValue;
18use siderite_validation::{
19    Dump, Schema, SchemaObject, SchemaRegistry, Validate, ValidationContext,
20};
21use std::sync::Arc;
22
23/// A many-to-one reference to `T` stored as `T`'s primary key.
24///
25/// Serializes as the bare key (`"author": 7`), like Django REST framework's
26/// default. The cached object is never serialized. [`Validate`], [`Schema`]
27/// and [`Dump`] follow `T::Pk`.
28pub struct ForeignKey<T: Model> {
29    id: T::Pk,
30    cached: Option<Arc<T>>,
31}
32
33/// A one-to-one reference: a unique [`ForeignKey`].
34pub type OneToOne<T> = ForeignKey<T>;
35
36impl<T: Model> ForeignKey<T> {
37    /// Reference by primary key (nothing is loaded).
38    pub fn new(id: T::Pk) -> Self {
39        Self { id, cached: None }
40    }
41
42    /// Reference an already loaded object; it becomes the cached value.
43    pub fn to(object: T) -> Self {
44        Self {
45            id: object.pk(),
46            cached: Some(Arc::new(object)),
47        }
48    }
49
50    /// The referenced primary key.
51    pub fn id(&self) -> &T::Pk {
52        &self.id
53    }
54
55    /// The cached object, if it was loaded.
56    pub fn cached(&self) -> Option<&T> {
57        self.cached.as_deref()
58    }
59
60    /// The referenced object: the cached one, or fetched by primary key (and
61    /// not cached, since `self` is borrowed immutably).
62    ///
63    /// # Errors
64    /// [`QueryError::DoesNotExist`](crate::QueryError::DoesNotExist) for a
65    /// dangling key, or backend errors.
66    pub async fn get(&self, db: &Db) -> Result<Arc<T>, OrmError> {
67        if let Some(object) = &self.cached {
68            return Ok(Arc::clone(object));
69        }
70        let pk = T::META.pk().ok_or_else(|| {
71            crate::QueryError::Model(format!("{} has no primary key", T::META.name))
72        })?;
73        T::objects(db)
74            .get(crate::Expr::col(pk.column).eq(self.id.to_value()))
75            .await
76            .map(Arc::new)
77    }
78
79    /// Replace the cached object. Ignored unless its key matches [`id`](Self::id),
80    /// so a stale object is never attached.
81    pub fn set_cached(&mut self, object: Arc<T>) {
82        if object.pk() == self.id {
83            self.cached = Some(object);
84        }
85    }
86}
87
88impl<T: Model> Clone for ForeignKey<T> {
89    fn clone(&self) -> Self {
90        Self {
91            id: self.id.clone(),
92            cached: self.cached.clone(),
93        }
94    }
95}
96
97impl<T: Model> std::fmt::Debug for ForeignKey<T> {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("ForeignKey")
100            .field("model", &T::META.name)
101            .field("id", &self.id)
102            .field("loaded", &self.cached.is_some())
103            .finish()
104    }
105}
106
107/// Equality compares keys only.
108impl<T: Model> PartialEq for ForeignKey<T> {
109    fn eq(&self, other: &Self) -> bool {
110        self.id == other.id
111    }
112}
113
114impl<T: Model> DbType for ForeignKey<T> {
115    const SQL_TYPE: SqlType = <T::Pk as DbType>::SQL_TYPE;
116    fn to_value(&self) -> Value {
117        self.id.to_value()
118    }
119    fn from_value(value: Value) -> Result<Self, String> {
120        T::Pk::from_value(value).map(Self::new)
121    }
122}
123
124impl<T: Model> Serialize for ForeignKey<T>
125where
126    T::Pk: Serialize,
127{
128    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
129        self.id.serialize(serializer)
130    }
131}
132
133impl<'de, T: Model> Deserialize<'de> for ForeignKey<T>
134where
135    T::Pk: Deserialize<'de>,
136{
137    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
138        T::Pk::deserialize(deserializer).map(Self::new)
139    }
140}
141
142impl<T: Model> Validate for ForeignKey<T>
143where
144    T::Pk: Validate,
145{
146    fn prepare(input: &mut JsonValue, ctx: &mut ValidationContext) {
147        T::Pk::prepare(input, ctx);
148    }
149
150    fn validate(&self, ctx: &mut ValidationContext) {
151        self.id.validate(ctx);
152    }
153}
154
155impl<T: Model + 'static> Schema for ForeignKey<T>
156where
157    T::Pk: Schema + 'static,
158{
159    fn schema(registry: &mut SchemaRegistry) -> SchemaObject {
160        registry.subschema::<T::Pk>()
161    }
162}
163
164impl<T: Model> Dump for ForeignKey<T> where T::Pk: Serialize {}
165
166/// Accessor for one many-to-many relation of one source object
167/// (`post.tags(&db)`). Generated accessors construct it; its query and
168/// mutation methods (`all`, `add`, `remove`, `clear`, `set`) run on `db`.
169pub struct ManyToManyManager<S: Model, T: Model> {
170    db: Db,
171    meta: &'static ManyToManyMeta,
172    source_pk: Value,
173    _models: std::marker::PhantomData<fn() -> (S, T)>,
174}
175
176impl<S: Model, T: Model> ManyToManyManager<S, T> {
177    /// Manager for relation `meta` of the object whose key is `source_pk`.
178    pub fn new(db: &Db, meta: &'static ManyToManyMeta, source_pk: Value) -> Self {
179        Self {
180            db: db.clone(),
181            meta,
182            source_pk,
183            _models: std::marker::PhantomData,
184        }
185    }
186
187    /// Database handle.
188    pub fn db(&self) -> &Db {
189        &self.db
190    }
191
192    /// Relation metadata.
193    pub fn meta(&self) -> &'static ManyToManyMeta {
194        self.meta
195    }
196
197    /// Key of the source object.
198    pub fn source_pk(&self) -> &Value {
199        &self.source_pk
200    }
201}