Skip to main content

vantage_table/references/
one.rs

1//! HasOne — one-to-one relationship where the foreign key is on the source table.
2//!
3//! Example: Client has one Bakery (via client.bakery_id)
4
5use std::fmt::Display;
6use std::{any::Any, marker::PhantomData, sync::Arc};
7
8use vantage_core::{Result, error};
9use vantage_types::{EmptyEntity, Entity, Record};
10
11use crate::{
12    references::Reference,
13    table::Table,
14    traits::{column_like::ColumnLike, table_source::TableSource},
15};
16
17pub struct HasOne<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> {
18    /// Foreign key column on the source table (e.g. "bakery_id")
19    foreign_key: String,
20    /// Factory: given a data source, produce the target table
21    build_target: Arc<dyn Fn(T) -> Table<T, TargetE> + Send + Sync>,
22    _phantom: PhantomData<SourceE>,
23}
24
25impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>>
26    HasOne<T, SourceE, TargetE>
27{
28    pub fn new(
29        foreign_key: impl Into<String>,
30        build_target: impl Fn(T) -> Table<T, TargetE> + Send + Sync + 'static,
31    ) -> Self {
32        Self {
33            foreign_key: foreign_key.into(),
34            build_target: Arc::new(build_target),
35            _phantom: PhantomData,
36        }
37    }
38}
39
40impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> Clone
41    for HasOne<T, SourceE, TargetE>
42{
43    fn clone(&self) -> Self {
44        Self {
45            foreign_key: self.foreign_key.clone(),
46            build_target: self.build_target.clone(),
47            _phantom: PhantomData,
48        }
49    }
50}
51
52impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> std::fmt::Debug
53    for HasOne<T, SourceE, TargetE>
54{
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.debug_struct("HasOne")
57            .field("foreign_key", &self.foreign_key)
58            .finish()
59    }
60}
61
62impl<
63    T: TableSource + 'static,
64    SourceE: Entity<T::Value> + 'static,
65    TargetE: Entity<T::Value> + 'static,
66> Reference for HasOne<T, SourceE, TargetE>
67where
68    T::Value: Into<ciborium::Value> + From<ciborium::Value>,
69    T::Id: Display + From<String>,
70{
71    fn columns(&self, _source_id: &str, target_id: &str) -> (String, String) {
72        (self.foreign_key.clone(), target_id.to_string())
73    }
74
75    fn build_target(&self, data_source: &dyn Any) -> Box<dyn Any> {
76        let ds = data_source
77            .downcast_ref::<T>()
78            .expect("data source type mismatch in HasOne::build_target");
79        Box::new((self.build_target)(ds.clone()))
80    }
81
82    fn cardinality(&self) -> vantage_vista::ReferenceKind {
83        vantage_vista::ReferenceKind::HasOne
84    }
85
86    fn resolve_from_row(
87        &self,
88        data_source: &dyn Any,
89        _source_id_field: &str,
90        source_row: &dyn Any,
91    ) -> Result<Box<dyn Any>> {
92        let ds = data_source
93            .downcast_ref::<T>()
94            .ok_or_else(|| error!("data source type mismatch in HasOne::resolve_from_row"))?;
95        let row = source_row
96            .downcast_ref::<Record<T::Value>>()
97            .ok_or_else(|| error!("source row type mismatch in HasOne::resolve_from_row"))?;
98
99        let mut target = (self.build_target)(ds.clone());
100        let target_id = target
101            .id_field()
102            .map(|c| c.name().to_string())
103            .unwrap_or_else(|| "id".to_string());
104
105        let (src_col, tgt_col) = self.columns("", &target_id);
106        let join_value = row.get(&src_col).cloned().ok_or_else(|| {
107            error!(
108                "source row missing foreign-key field",
109                field = src_col.as_str()
110            )
111        })?;
112
113        let condition = ds.eq_value_condition(&tgt_col, join_value)?;
114        target.add_condition(condition);
115
116        Ok(Box::new(target.into_entity::<EmptyEntity>()))
117    }
118
119    fn target_type_name(&self) -> &'static str {
120        std::any::type_name::<Table<T, TargetE>>()
121    }
122}