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    any::AnyTable,
13    references::Reference,
14    table::Table,
15    traits::{column_like::ColumnLike, table_source::TableSource},
16};
17
18pub struct HasOne<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> {
19    /// Foreign key column on the source table (e.g. "bakery_id")
20    foreign_key: String,
21    /// Factory: given a data source, produce the target table
22    build_target: Arc<dyn Fn(T) -> Table<T, TargetE> + Send + Sync>,
23    _phantom: PhantomData<SourceE>,
24}
25
26impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>>
27    HasOne<T, SourceE, TargetE>
28{
29    pub fn new(
30        foreign_key: impl Into<String>,
31        build_target: impl Fn(T) -> Table<T, TargetE> + Send + Sync + 'static,
32    ) -> Self {
33        Self {
34            foreign_key: foreign_key.into(),
35            build_target: Arc::new(build_target),
36            _phantom: PhantomData,
37        }
38    }
39}
40
41impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> Clone
42    for HasOne<T, SourceE, TargetE>
43{
44    fn clone(&self) -> Self {
45        Self {
46            foreign_key: self.foreign_key.clone(),
47            build_target: self.build_target.clone(),
48            _phantom: PhantomData,
49        }
50    }
51}
52
53impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> std::fmt::Debug
54    for HasOne<T, SourceE, TargetE>
55{
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct("HasOne")
58            .field("foreign_key", &self.foreign_key)
59            .finish()
60    }
61}
62
63impl<
64    T: TableSource + 'static,
65    SourceE: Entity<T::Value> + 'static,
66    TargetE: Entity<T::Value> + 'static,
67> Reference for HasOne<T, SourceE, TargetE>
68where
69    T::Value: Into<ciborium::Value> + From<ciborium::Value>,
70    T::Id: Display + From<String>,
71{
72    fn columns(&self, _source_id: &str, target_id: &str) -> (String, String) {
73        (self.foreign_key.clone(), target_id.to_string())
74    }
75
76    fn build_target(&self, data_source: &dyn Any) -> Box<dyn Any> {
77        let ds = data_source
78            .downcast_ref::<T>()
79            .expect("data source type mismatch in HasOne::build_target");
80        Box::new((self.build_target)(ds.clone()))
81    }
82
83    fn cardinality(&self) -> vantage_vista::ReferenceKind {
84        vantage_vista::ReferenceKind::HasOne
85    }
86
87    fn resolve_from_row(
88        &self,
89        data_source: &dyn Any,
90        _source_id_field: &str,
91        source_row: &dyn Any,
92    ) -> Result<Box<dyn Any>> {
93        let ds = data_source
94            .downcast_ref::<T>()
95            .ok_or_else(|| error!("data source type mismatch in HasOne::resolve_from_row"))?;
96        let row = source_row
97            .downcast_ref::<Record<T::Value>>()
98            .ok_or_else(|| error!("source row type mismatch in HasOne::resolve_from_row"))?;
99
100        let mut target = (self.build_target)(ds.clone());
101        let target_id = target
102            .id_field()
103            .map(|c| c.name().to_string())
104            .unwrap_or_else(|| "id".to_string());
105
106        let (src_col, tgt_col) = self.columns("", &target_id);
107        let join_value = row.get(&src_col).cloned().ok_or_else(|| {
108            error!(
109                "source row missing foreign-key field",
110                field = src_col.as_str()
111            )
112        })?;
113
114        let condition = ds.eq_value_condition(&tgt_col, join_value)?;
115        target.add_condition(condition);
116
117        Ok(Box::new(target.into_entity::<EmptyEntity>()))
118    }
119
120    fn resolve_as_any(&self, source_table: &dyn Any) -> Result<AnyTable> {
121        let source = source_table
122            .downcast_ref::<Table<T, SourceE>>()
123            .ok_or_else(|| {
124                vantage_core::error!("Source table type mismatch in HasOne::resolve_as_any")
125            })?;
126
127        let source_id = source
128            .id_field()
129            .map(|c| c.name().to_string())
130            .unwrap_or_else(|| "id".to_string());
131
132        let mut target = (self.build_target)(source.data_source().clone());
133
134        let target_id = target
135            .id_field()
136            .map(|c| c.name().to_string())
137            .unwrap_or_else(|| "id".to_string());
138
139        let (src_col, tgt_col) = self.columns(&source_id, &target_id);
140        let condition = source
141            .data_source()
142            .related_in_condition(&tgt_col, source, &src_col);
143        target.add_condition(condition);
144
145        Ok(AnyTable::from_table(target))
146    }
147
148    fn target_type_name(&self) -> &'static str {
149        std::any::type_name::<Table<T, TargetE>>()
150    }
151}