Skip to main content

vantage_table/references/
foreign.rs

1//! HasForeign — cross-persistence reference with user-provided resolution.
2//!
3//! The user supplies a sync closure that receives the source table and returns
4//! an AnyTable with deferred conditions attached. The actual async work
5//! (fetching IDs, querying the foreign backend) happens lazily at query time
6//! via DeferredFn inside the target's condition system.
7
8use std::{any::Any, marker::PhantomData, sync::Arc};
9
10use vantage_core::Result;
11use vantage_types::Entity;
12
13use crate::{
14    any::AnyTable, references::Reference, table::Table, traits::table_source::TableSource,
15};
16
17type ResolveFn<T, E> = dyn Fn(&Table<T, E>) -> Result<AnyTable> + Send + Sync;
18
19pub struct HasForeign<T: TableSource, SourceE: Entity<T::Value>> {
20    /// Sync closure: receives source table, returns AnyTable with deferred conditions
21    resolve: Arc<ResolveFn<T, SourceE>>,
22    /// Type name for error messages
23    target_type: &'static str,
24    _phantom: PhantomData<(T, SourceE)>,
25}
26
27impl<T: TableSource + 'static, SourceE: Entity<T::Value> + 'static> HasForeign<T, SourceE> {
28    pub fn new(
29        target_type: &'static str,
30        resolve: impl Fn(&Table<T, SourceE>) -> Result<AnyTable> + Send + Sync + 'static,
31    ) -> Self {
32        Self {
33            resolve: Arc::new(resolve),
34            target_type,
35            _phantom: PhantomData,
36        }
37    }
38}
39
40impl<T: TableSource, SourceE: Entity<T::Value>> Clone for HasForeign<T, SourceE> {
41    fn clone(&self) -> Self {
42        Self {
43            resolve: self.resolve.clone(),
44            target_type: self.target_type,
45            _phantom: PhantomData,
46        }
47    }
48}
49
50impl<T: TableSource, SourceE: Entity<T::Value>> std::fmt::Debug for HasForeign<T, SourceE> {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.debug_struct("HasForeign")
53            .field("target_type", &self.target_type)
54            .finish()
55    }
56}
57
58impl<T: TableSource + 'static, SourceE: Entity<T::Value> + 'static> Reference
59    for HasForeign<T, SourceE>
60{
61    fn columns(&self, _source_id: &str, _target_id: &str) -> (String, String) {
62        unreachable!("columns() should not be called on foreign references")
63    }
64
65    fn build_target(&self, _data_source: &dyn Any) -> Box<dyn Any> {
66        unreachable!("build_target() should not be called on foreign references")
67    }
68
69    fn is_foreign(&self) -> bool {
70        true
71    }
72
73    fn resolve_as_any(&self, source_table: &dyn Any) -> Result<AnyTable> {
74        let source = source_table
75            .downcast_ref::<Table<T, SourceE>>()
76            .ok_or_else(|| {
77                vantage_core::error!("Source table type mismatch in HasForeign::resolve_as_any")
78            })?;
79        (self.resolve)(source)
80    }
81
82    fn target_type_name(&self) -> &'static str {
83        self.target_type
84    }
85}