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}