use std::marker::PhantomData;
use serde::{Deserialize, Serialize};
use super::Model;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Side {
Parent,
Child,
}
#[derive(Debug, Clone)]
pub struct OneToOne<C: Model> {
fk: Option<C::PrimaryKey>,
parent_pk: Option<serde_json::Value>,
resolved: Option<Box<C>>,
loaded: bool,
side: Side,
_phantom: PhantomData<C>,
}
impl<C: Model> Default for OneToOne<C> {
fn default() -> Self {
Self::empty()
}
}
impl<C: Model> OneToOne<C> {
pub fn empty() -> Self {
Self {
fk: None,
parent_pk: None,
resolved: None,
loaded: false,
side: Side::Parent,
_phantom: PhantomData,
}
}
pub fn new(id: C::PrimaryKey) -> Self {
Self {
fk: Some(id),
parent_pk: None,
resolved: None,
loaded: false,
side: Side::Child,
_phantom: PhantomData,
}
}
pub fn id(&self) -> C::PrimaryKey {
self.fk
.clone()
.expect("OneToOne::id called on an unset slot — construct with OneToOne::new(id)")
}
pub fn resolved(&self) -> Option<&C> {
self.resolved.as_deref()
}
pub fn is_loaded(&self) -> bool {
self.loaded
}
pub fn parent_id(&self) -> Option<&serde_json::Value> {
self.parent_pk.as_ref()
}
pub fn set_parent_id(&mut self, id: serde_json::Value) {
self.parent_pk = Some(id);
}
pub fn set_resolved(&mut self, row: C) {
self.resolved = Some(Box::new(row));
self.loaded = true;
}
pub fn set_resolved_opt(&mut self, row: Option<C>) {
self.resolved = row.map(Box::new);
self.loaded = true;
}
}
impl<C: Model + Serialize> Serialize for OneToOne<C>
where
C::PrimaryKey: Serialize,
{
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if let Some(row) = &self.resolved {
return row.serialize(s);
}
match self.side {
Side::Child => match &self.fk {
Some(id) => id.serialize(s),
None => s.serialize_none(),
},
Side::Parent => s.serialize_none(),
}
}
}
impl<'de, C: Model + Deserialize<'de>> Deserialize<'de> for OneToOne<C> {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let opt = Option::<C>::deserialize(d).unwrap_or(None);
let loaded = opt.is_some();
Ok(Self {
fk: None,
parent_pk: None,
resolved: opt.map(Box::new),
loaded,
side: Side::Parent,
_phantom: PhantomData,
})
}
}
impl<C: Model> sqlx::Type<sqlx::Sqlite> for OneToOne<C>
where
C::PrimaryKey: sqlx::Type<sqlx::Sqlite>,
{
fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
<C::PrimaryKey as sqlx::Type<sqlx::Sqlite>>::type_info()
}
fn compatible(ty: &sqlx::sqlite::SqliteTypeInfo) -> bool {
<C::PrimaryKey as sqlx::Type<sqlx::Sqlite>>::compatible(ty)
}
}
impl<C: Model> sqlx::Type<sqlx::Postgres> for OneToOne<C>
where
C::PrimaryKey: sqlx::Type<sqlx::Postgres>,
{
fn type_info() -> sqlx::postgres::PgTypeInfo {
<C::PrimaryKey as sqlx::Type<sqlx::Postgres>>::type_info()
}
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
<C::PrimaryKey as sqlx::Type<sqlx::Postgres>>::compatible(ty)
}
}
impl<'r, C: Model> sqlx::Decode<'r, sqlx::Sqlite> for OneToOne<C>
where
C::PrimaryKey: sqlx::Decode<'r, sqlx::Sqlite>,
{
fn decode(
value: sqlx::sqlite::SqliteValueRef<'r>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let raw = <C::PrimaryKey as sqlx::Decode<sqlx::Sqlite>>::decode(value)?;
Ok(Self::new(raw))
}
}
impl<'r, C: Model> sqlx::Decode<'r, sqlx::Postgres> for OneToOne<C>
where
C::PrimaryKey: sqlx::Decode<'r, sqlx::Postgres>,
{
fn decode(
value: sqlx::postgres::PgValueRef<'r>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let raw = <C::PrimaryKey as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
Ok(Self::new(raw))
}
}
impl<'q, C: Model> sqlx::Encode<'q, sqlx::Sqlite> for OneToOne<C>
where
C::PrimaryKey: sqlx::Encode<'q, sqlx::Sqlite> + Clone + Default,
{
fn encode_by_ref(
&self,
buf: &mut <sqlx::Sqlite as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
let id = self.fk.clone().unwrap_or_default();
<C::PrimaryKey as sqlx::Encode<'q, sqlx::Sqlite>>::encode_by_ref(&id, buf)
}
}
impl<'q, C: Model> sqlx::Encode<'q, sqlx::Postgres> for OneToOne<C>
where
C::PrimaryKey: sqlx::Encode<'q, sqlx::Postgres> + Clone + Default,
{
fn encode_by_ref(
&self,
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
let id = self.fk.clone().unwrap_or_default();
<C::PrimaryKey as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&id, buf)
}
}