use crate::model::Model;
use crate::relation::foreign_key::{ForeignKey, ForeignKeyResolved};
use bytes::BytesMut;
use postgres_types::{FromSql, IsNull, ToSql, Type};
pub struct OneToOneField<T: Model>(ForeignKey<T>);
impl<T: Model> Clone for OneToOneField<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Model> Copy for OneToOneField<T> where T::Pk: Copy {}
impl<T: Model> PartialEq for OneToOneField<T>
where
T::Pk: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T: Model> Eq for OneToOneField<T> where T::Pk: Eq {}
impl<T: Model> std::hash::Hash for OneToOneField<T>
where
T::Pk: std::hash::Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T: Model> std::fmt::Debug for OneToOneField<T>
where
T::Pk: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "OneToOneField<{}>({:?})", T::table_name(), self.0.key())
}
}
impl<T: Model> OneToOneField<T> {
#[inline]
pub fn new(key: T::Pk) -> Self {
Self(ForeignKey::new(key))
}
#[inline]
pub fn key(&self) -> T::Pk
where
T::Pk: Clone,
{
self.0.key()
}
#[inline]
pub fn resolved(&self) -> Option<&T> {
self.0.resolved()
}
pub async fn fetch(
&self,
ctx: &mut crate::context::DjogiContext,
) -> Result<T, crate::DjogiError>
where
T::Pk: Clone,
{
self.0.fetch(ctx).await
}
}
impl<T: Model> ToSql for OneToOneField<T>
where
T::Pk: ToSql,
{
fn to_sql(
&self,
ty: &Type,
out: &mut BytesMut,
) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
self.0.to_sql(ty, out)
}
fn accepts(ty: &Type) -> bool {
<T::Pk as ToSql>::accepts(ty)
}
postgres_types::to_sql_checked!();
}
impl<'a, T: Model> FromSql<'a> for OneToOneField<T>
where
T::Pk: FromSql<'a>,
{
fn from_sql(
ty: &Type,
raw: &'a [u8],
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
let pk = <T::Pk as FromSql<'a>>::from_sql(ty, raw)?;
Ok(OneToOneField::new(pk))
}
fn accepts(ty: &Type) -> bool {
<T::Pk as FromSql<'a>>::accepts(ty)
}
}
impl<T: Model> crate::query::field::IntoFilterValue for OneToOneField<T>
where
T::Pk: crate::query::field::IntoFilterValue + Clone,
{
fn into_filter_value(self) -> crate::query::condition::FilterValue {
self.0.into_filter_value()
}
fn jsonb_sql_cast() -> Option<crate::jsonb::JsonbSqlCast> {
<T::Pk as crate::query::field::IntoFilterValue>::jsonb_sql_cast()
}
}
impl<T: Model + 'static> crate::query::field::DjogiPortableEq for OneToOneField<T> where
T::Pk: crate::query::field::DjogiPortableEq
{
}
impl<T: Model> serde::Serialize for OneToOneField<T>
where
T::Pk: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de, T: Model> serde::Deserialize<'de> for OneToOneField<T>
where
T::Pk: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
<ForeignKey<T> as serde::Deserialize<'de>>::deserialize(deserializer).map(OneToOneField)
}
}
pub struct OneToOneFieldResolved<T: Model>(ForeignKeyResolved<T>);
impl<T: Model + Clone> Clone for OneToOneFieldResolved<T>
where
T::Pk: Clone,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Model + std::fmt::Debug> std::fmt::Debug for OneToOneFieldResolved<T>
where
T::Pk: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OneToOneFieldResolved")
.field(&self.0)
.finish()
}
}
impl<T: Model> OneToOneFieldResolved<T> {
#[allow(dead_code)]
pub(crate) fn new(key: T::Pk, child: Option<T>) -> Self {
Self(ForeignKeyResolved::new(key, child))
}
#[inline]
pub fn key(&self) -> &T::Pk {
self.0.key()
}
#[inline]
pub fn resolved(&self) -> Option<&T> {
self.0.resolved()
}
#[inline]
pub fn expect_resolved(
&self,
model: &'static str,
field: &'static str,
) -> Result<&T, crate::DjogiError> {
self.0.expect_resolved(model, field)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DjogiError;
use crate::types::HeerId;
#[derive(Debug, Clone)]
struct Dummy;
impl crate::model::__sealed::Sealed for Dummy {}
#[allow(clippy::manual_async_fn)]
impl crate::model::Model for Dummy {
type Pk = HeerId;
type Fields = ();
fn table_name() -> &'static str {
"dummies"
}
fn pk_value(&self) -> &HeerId {
unreachable!()
}
fn descriptor() -> &'static crate::descriptor::ModelDescriptor {
unreachable!()
}
fn get(
_ctx: &mut crate::context::DjogiContext,
_id: HeerId,
) -> impl std::future::Future<Output = Result<Self, DjogiError>> + Send {
async { unreachable!() }
}
fn create(
_ctx: &mut crate::context::DjogiContext,
_v: Self,
) -> impl std::future::Future<Output = Result<Self, DjogiError>> + Send {
async { unreachable!() }
}
fn save<'ctx>(
&'ctx mut self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<(), DjogiError>> + Send + 'ctx {
async { unreachable!() }
}
fn delete(
self,
_ctx: &mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<(), DjogiError>> + Send {
async { unreachable!() }
}
fn refresh_from_db<'ctx>(
&'ctx self,
_ctx: &'ctx mut crate::context::DjogiContext,
) -> impl std::future::Future<Output = Result<Self, DjogiError>> + Send + 'ctx {
async { unreachable!() }
}
}
#[test]
fn one_to_one_field_stores_and_returns_key() {
let o: OneToOneField<Dummy> = OneToOneField::new(HeerId::from_i64(77).unwrap());
assert_eq!(o.key(), HeerId::from_i64(77).unwrap());
assert!(o.resolved().is_none());
}
#[test]
fn one_to_one_field_is_copy_when_pk_copy() {
fn takes_copy<T: Copy>(_: T) {}
let o: OneToOneField<Dummy> = OneToOneField::new(HeerId::from_i64(1).unwrap());
takes_copy(o);
let _again = o;
}
#[test]
fn one_to_one_field_resolved_expect_resolved_err_on_missing() {
let r: OneToOneFieldResolved<Dummy> =
OneToOneFieldResolved::new(HeerId::from_i64(1).unwrap(), None);
let err = r.expect_resolved("Profile", "user_id").unwrap_err();
assert!(matches!(
err,
DjogiError::RelationUnloaded {
model: "Profile",
field: "user_id"
}
));
}
#[test]
fn one_to_one_field_resolved_expect_resolved_ok_on_present() {
let r: OneToOneFieldResolved<Dummy> =
OneToOneFieldResolved::new(HeerId::from_i64(1).unwrap(), Some(Dummy));
assert!(r.expect_resolved("Profile", "user_id").is_ok());
assert!(r.resolved().is_some());
}
#[test]
fn one_to_one_field_resolved_clone() {
let r: OneToOneFieldResolved<Dummy> =
OneToOneFieldResolved::new(HeerId::from_i64(5).unwrap(), Some(Dummy));
let r2 = r.clone();
assert!(r.resolved().is_some());
assert!(r2.resolved().is_some());
assert_eq!(r.key(), r2.key());
}
#[test]
fn one_to_one_field_resolved_key_borrow() {
let pk = HeerId::from_i64(123).unwrap();
let r: OneToOneFieldResolved<Dummy> = OneToOneFieldResolved::new(pk, None);
assert_eq!(r.key(), &pk);
}
#[test]
fn one_to_one_field_serializes_as_wrapped_pk() {
let o: OneToOneField<Dummy> = OneToOneField::new(HeerId::from_i64(42).unwrap());
let json = serde_json::to_string(&o).expect("serialize");
let pk_json = serde_json::to_string(&HeerId::from_i64(42).unwrap()).expect("pk serialize");
assert_eq!(json, pk_json);
}
#[test]
fn one_to_one_field_round_trips_through_json() {
let o: OneToOneField<Dummy> = OneToOneField::new(HeerId::from_i64(7).unwrap());
let json = serde_json::to_string(&o).expect("serialize");
let restored: OneToOneField<Dummy> = serde_json::from_str(&json).expect("deserialize");
assert_eq!(restored, o);
}
}