Skip to main content

vantage_vista/spec/
lowering.rs

1//! Spec lookups every driver factory makes while lowering a [`VistaSpec`].
2
3use std::sync::Arc;
4
5use vantage_core::{Result, error};
6
7use super::{ColumnSpec, ReferenceSpec, VistaSpec};
8use crate::column::ColumnFlag;
9use crate::reference::{JoinKey, Reference, ReferenceKind};
10
11/// Looks up a spec by vista name. Factories hold it as
12/// `Arc<dyn SpecResolver<S>>` and hand clones into each reference, so a child
13/// table is rebuilt from the current spec at traversal time. Any
14/// `Fn(&str) -> Option<S>` closure is a resolver.
15pub trait SpecResolver<S>: Send + Sync {
16    fn resolve(&self, name: &str) -> Option<S>;
17}
18
19impl<S, F: Fn(&str) -> Option<S> + Send + Sync> SpecResolver<S> for F {
20    fn resolve(&self, name: &str) -> Option<S> {
21        self(name)
22    }
23}
24
25impl<T, C, R> VistaSpec<T, C, R> {
26    /// The id column: `id_column:`, else the first column flagged `id`, else
27    /// `default`, the driver's own convention (`"id"`, MongoDB's `"_id"`).
28    pub fn resolve_id_column(&self, default: &str) -> String {
29        if let Some(id) = &self.id_column {
30            return id.clone();
31        }
32        self.columns
33            .iter()
34            .find(|(_, col)| col.has_flag(ColumnFlag::Id))
35            .map_or_else(|| default.to_string(), |(name, _)| name.clone())
36    }
37}
38
39impl<C> ColumnSpec<C> {
40    pub fn has_flag(&self, flag: impl Into<ColumnFlag>) -> bool {
41        self.flags.contains(&flag.into())
42    }
43}
44
45impl<R> ReferenceSpec<R> {
46    /// The `foreign_key:` column, else `relation`, the name the reference is
47    /// declared under.
48    pub fn foreign_key(&self, relation: &str) -> String {
49        self.foreign_key
50            .clone()
51            .unwrap_or_else(|| relation.to_string())
52    }
53
54    /// The join this entry declares under `relation`. Its `keys:` when given
55    /// (each pair reads a parent-row column and constrains a target column),
56    /// else the single [`foreign_key`](Self::foreign_key): on the parent for
57    /// `has_one` (matched to the target's id), on the target for `has_many`
58    /// (matched to the parent's `source_id`). A contained kind is refused:
59    /// contained relations are declared under `contained:`.
60    pub fn to_reference(&self, relation: &str, source_id: &str) -> Result<Reference> {
61        let mut keys = self.keys.iter().cloned();
62        let first = keys.next();
63        let target = self.table.clone();
64        let join = match self.kind {
65            ReferenceKind::HasOne => Reference::has_one(
66                relation,
67                target,
68                first.unwrap_or_else(|| JoinKey::to_target_id(self.foreign_key(relation))),
69            ),
70            ReferenceKind::HasMany => Reference::has_many(
71                relation,
72                target,
73                first.unwrap_or_else(|| JoinKey::new(source_id, self.foreign_key(relation))),
74            ),
75            _ => {
76                return Err(error!(
77                    "references: entries must be has_one or has_many",
78                    relation = relation
79                ));
80            }
81        };
82        Ok(keys.fold(join, Reference::with_key))
83    }
84}
85
86/// Resolve the spec a `base:` vista derives from. Errors when no resolver is
87/// attached or the base isn't known to it.
88pub fn resolve_base_spec<S>(
89    resolver: Option<Arc<dyn SpecResolver<S>>>,
90    base: &str,
91) -> Result<(Arc<dyn SpecResolver<S>>, S)> {
92    let resolver = resolver.ok_or_else(|| {
93        error!(
94            "vista declares `base:` but no spec resolver is attached to the factory",
95            base = base
96        )
97    })?;
98    let spec = resolver
99        .resolve(base)
100        .ok_or_else(|| error!("base vista not found via resolver", base = base))?;
101    Ok((resolver, spec))
102}