Skip to main content

vantage_vista/
reference.rs

1use serde::{Deserialize, Serialize};
2
3use crate::column::Column;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Reference {
7    pub name: String,
8    pub target: String,
9    pub kind: ReferenceKind,
10    pub foreign_key: String,
11    /// Optional Rhai script that *builds* the traversal target itself, in place
12    /// of the default foreign-key eq-condition path. Evaluated lazily at
13    /// traversal time with the parent `row` in scope (see the
14    /// `rhai_conventional` module, available with the `rhai` feature). `None`
15    /// keeps the conventional FK path.
16    /// Lowered from the per-reference YAML extras slot by each backend factory.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub build_script: Option<String>,
19}
20
21/// Schema for a **contained** relation — records embedded in a column of the
22/// parent row rather than stored in a separate table.
23///
24/// Unlike [`Reference`] (which names a foreign-key column), a contained
25/// relation names the **host column** holding the embedded data, carries the
26/// contained record's own column schema, and an optional id column. With no id
27/// column, contained-many records are addressed by positional index.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ContainedSpec {
30    pub name: String,
31    /// Column on the parent row holding the embedded object (one) or array
32    /// of objects (many).
33    pub host_column: String,
34    /// [`ContainsOne`](ContainedKind::ContainsOne) or
35    /// [`ContainsMany`](ContainedKind::ContainsMany).
36    pub kind: ContainedKind,
37    /// Columns of the contained record.
38    #[serde(default, skip_serializing_if = "Vec::is_empty")]
39    pub columns: Vec<Column>,
40    /// Field used as the contained record's id. `None` → positional index
41    /// (contained-many) or the fixed relation name (contained-one).
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub id_column: Option<String>,
44}
45
46impl ContainedSpec {
47    pub fn new(
48        name: impl Into<String>,
49        host_column: impl Into<String>,
50        kind: ContainedKind,
51    ) -> Self {
52        Self {
53            name: name.into(),
54            host_column: host_column.into(),
55            kind,
56            columns: Vec::new(),
57            id_column: None,
58        }
59    }
60
61    pub fn with_columns(mut self, columns: Vec<Column>) -> Self {
62        self.columns = columns;
63        self
64    }
65
66    pub fn with_id_column(mut self, id_column: impl Into<String>) -> Self {
67        self.id_column = Some(id_column.into());
68        self
69    }
70}
71
72impl Reference {
73    pub fn new(
74        name: impl Into<String>,
75        target: impl Into<String>,
76        kind: ReferenceKind,
77        foreign_key: impl Into<String>,
78    ) -> Self {
79        Self {
80            name: name.into(),
81            target: target.into(),
82            kind,
83            foreign_key: foreign_key.into(),
84            build_script: None,
85        }
86    }
87
88    /// Attach a Rhai build script that constructs the traversal target,
89    /// overriding the default foreign-key eq-condition path. See
90    /// [`build_script`](Self::build_script).
91    pub fn with_build_script(mut self, script: impl Into<String>) -> Self {
92        self.build_script = Some(script.into());
93        self
94    }
95}
96
97/// Cardinality of a relation. Cross-persistence-ness is no longer
98/// encoded here — it's determined at resolution time by whether the
99/// target Vista lives in the same driver or a different one (the
100/// inventory loader knows). YAML specs that previously used
101/// `kind: has_foreign` migrate to `kind: has_one` or `kind: has_many`.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub enum ReferenceKind {
105    #[default]
106    HasOne,
107    HasMany,
108}
109
110/// Cardinality of a **contained** relation — records embedded in a column of
111/// the parent row. Kept separate from [`ReferenceKind`] (which names
112/// foreign-key references) so contained relations don't leak into the
113/// foreign-key code paths: a contained relation is not a join, it's a view
114/// onto one column.
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case")]
117pub enum ContainedKind {
118    /// One record embedded as an object in the host column (e.g. a product's
119    /// `inventory`).
120    #[default]
121    ContainsOne,
122    /// Many records embedded as an array in the host column (e.g. an order's
123    /// `lines`).
124    ContainsMany,
125}