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::{references::Reference, table::Table, traits::table_source::TableSource};
12
13pub struct HasOne<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> {
14    /// Foreign key column on the source table (e.g. "bakery_id")
15    foreign_key: String,
16    /// Factory: given a data source, produce the target table
17    build_target: Arc<dyn Fn(T) -> Table<T, TargetE> + Send + Sync>,
18    _phantom: PhantomData<SourceE>,
19}
20
21impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>>
22    HasOne<T, SourceE, TargetE>
23{
24    pub fn new(
25        foreign_key: impl Into<String>,
26        build_target: impl Fn(T) -> Table<T, TargetE> + Send + Sync + 'static,
27    ) -> Self {
28        Self {
29            foreign_key: foreign_key.into(),
30            build_target: Arc::new(build_target),
31            _phantom: PhantomData,
32        }
33    }
34}
35
36impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> Clone
37    for HasOne<T, SourceE, TargetE>
38{
39    fn clone(&self) -> Self {
40        Self {
41            foreign_key: self.foreign_key.clone(),
42            build_target: self.build_target.clone(),
43            _phantom: PhantomData,
44        }
45    }
46}
47
48impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> std::fmt::Debug
49    for HasOne<T, SourceE, TargetE>
50{
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.debug_struct("HasOne")
53            .field("foreign_key", &self.foreign_key)
54            .finish()
55    }
56}
57
58impl<
59    T: TableSource + 'static,
60    SourceE: Entity<T::Value> + 'static,
61    TargetE: Entity<T::Value> + 'static,
62> Reference for HasOne<T, SourceE, TargetE>
63where
64    T::Value: Into<ciborium::Value> + From<ciborium::Value>,
65    T::Id: Display + From<String>,
66{
67    fn columns(&self, _source_id: &str, target_id: &str) -> (String, String) {
68        (self.foreign_key.clone(), target_id.to_string())
69    }
70
71    fn foreign_key(&self) -> &str {
72        &self.foreign_key
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 build_target_erased(&self, data_source: &dyn Any) -> Box<dyn Any> {
83        let ds = data_source
84            .downcast_ref::<T>()
85            .expect("data source type mismatch in HasOne::build_target_erased");
86        Box::new((self.build_target)(ds.clone()).into_entity::<EmptyEntity>())
87    }
88
89    fn cardinality(&self) -> vantage_vista::ReferenceKind {
90        vantage_vista::ReferenceKind::HasOne
91    }
92
93    fn resolve_from_row(
94        &self,
95        data_source: &dyn Any,
96        _source_id_field: &str,
97        source_row: &dyn Any,
98    ) -> Result<Box<dyn Any>> {
99        let ds = data_source
100            .downcast_ref::<T>()
101            .ok_or_else(|| error!("data source type mismatch in HasOne::resolve_from_row"))?;
102        let row = source_row
103            .downcast_ref::<Record<T::Value>>()
104            .ok_or_else(|| error!("source row type mismatch in HasOne::resolve_from_row"))?;
105
106        let mut target = (self.build_target)(ds.clone());
107        let target_id = target.id_field_name();
108
109        let (src_col, tgt_col) = self.columns("", &target_id);
110        let join_value = row.get(&src_col).cloned().ok_or_else(|| {
111            error!(
112                "source row missing foreign-key field",
113                field = src_col.as_str()
114            )
115        })?;
116        // Rows arriving from a script/JSON surface carry ids in string form.
117        let join_value = ds.coerce_reference_value(join_value);
118
119        let condition = ds.eq_value_condition(&tgt_col, join_value.clone())?;
120        target.add_condition(condition);
121        // Conform inserts into the single-target set to the join value.
122        target.add_invariant(tgt_col, join_value);
123
124        Ok(Box::new(target.into_entity::<EmptyEntity>()))
125    }
126
127    fn target_type_name(&self) -> &'static str {
128        std::any::type_name::<Table<T, TargetE>>()
129    }
130}