Skip to main content

vantage_vista/reference/
narrow.rs

1//! [`Reference::narrow`]: narrowing a join's target to one parent row.
2
3use ciborium::Value as CborValue;
4use vantage_core::{Context, Result, VantageError, error};
5use vantage_types::Record;
6
7use super::{JoinKey, Reference};
8use crate::filter::FilterOp;
9use crate::vista::Vista;
10
11impl Reference {
12    /// Narrow this join's bare `target` to the rows related to `row`.
13    ///
14    /// Each key reads the parent's `from` column and constrains the target's
15    /// [`target_column`](JoinKey::target_column) to that value through
16    /// [`Vista::add_condition`]. The target's `query()` therefore records the
17    /// join, and the target must take the constrained column. A driver that
18    /// compares ids by type converts the value in its own `add_condition`.
19    ///
20    /// A contained relation, or a join whose public `keys` were emptied, has
21    /// nothing to narrow by and is an `IncorrectUsage` error. Other errors
22    /// carry `relation` and `column` context. A refusal (`Unsupported` or
23    /// `Unimplemented`, such as a computed join column) stays a refusal; any
24    /// other failure is a normal error.
25    pub fn narrow(&self, mut target: Vista, row: &Record<CborValue>) -> Result<Vista> {
26        if self.kind.is_contained() || self.keys.is_empty() {
27            return Err(error!(
28                "relation has no join keys to narrow by",
29                relation = self.name.as_str(),
30                kind = self.kind.as_str()
31            )
32            .mark_incorrect_usage());
33        }
34        for key in &self.keys {
35            self.narrow_key(&mut target, key, row)?;
36        }
37        Ok(target)
38    }
39
40    fn narrow_key(&self, target: &mut Vista, key: &JoinKey, row: &Record<CborValue>) -> Result<()> {
41        let relation = self.name.as_str();
42        // A flat key first (SQL's imported `author.name`), else a path into
43        // nested maps (a REST row's `pad.id` is `pad: { id }`).
44        let value = row
45            .get(&key.from)
46            .or_else(|| crate::eval::path_get(row, &key.from))
47            .ok_or_else(|| {
48                error!(
49                    "parent row lacks the join column",
50                    relation = relation,
51                    column = key.from.as_str()
52                )
53            })?;
54        let column = match key.target_column(target) {
55            Ok(column) => column.to_string(),
56            Err(e) => return Err(wrap(e, relation, &key.from)),
57        };
58        target
59            .add_condition(&column, FilterOp::Eq, value.clone())
60            .map_err(|e| wrap(e, relation, &column))
61    }
62}
63
64/// `err` as the source of a "can't narrow" error naming `relation` and
65/// `column`, keeping a refusal's kind.
66fn wrap(err: VantageError, relation: &str, column: &str) -> VantageError {
67    let (unsupported, unimplemented) = (err.is_unsupported(), err.is_unimplemented());
68    let wrapped = Err::<(), _>(err)
69        .with_context(|| {
70            error!(
71                "can't narrow the relation's target to the parent row",
72                relation = relation,
73                column = column
74            )
75        })
76        .unwrap_err();
77    if unsupported {
78        wrapped.mark_unsupported()
79    } else if unimplemented {
80        wrapped.mark_unimplemented()
81    } else {
82        wrapped
83    }
84}