vantage-vista 0.5.2

Universal, schema-bearing data handle for the Vantage data framework
Documentation
use serde::{Deserialize, Serialize};

use crate::column::Column;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reference {
    pub name: String,
    pub target: String,
    pub kind: ReferenceKind,
    pub foreign_key: String,
}

/// Schema for a **contained** relation — records embedded in a column of the
/// parent row rather than stored in a separate table.
///
/// Unlike [`Reference`] (which names a foreign-key column), a contained
/// relation names the **host column** holding the embedded data, carries the
/// contained record's own column schema, and an optional id column. With no id
/// column, contained-many records are addressed by positional index.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainedSpec {
    pub name: String,
    /// Column on the parent row holding the embedded object (one) or array
    /// of objects (many).
    pub host_column: String,
    /// [`ContainsOne`](ContainedKind::ContainsOne) or
    /// [`ContainsMany`](ContainedKind::ContainsMany).
    pub kind: ContainedKind,
    /// Columns of the contained record.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub columns: Vec<Column>,
    /// Field used as the contained record's id. `None` → positional index
    /// (contained-many) or the fixed relation name (contained-one).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub id_column: Option<String>,
}

impl ContainedSpec {
    pub fn new(
        name: impl Into<String>,
        host_column: impl Into<String>,
        kind: ContainedKind,
    ) -> Self {
        Self {
            name: name.into(),
            host_column: host_column.into(),
            kind,
            columns: Vec::new(),
            id_column: None,
        }
    }

    pub fn with_columns(mut self, columns: Vec<Column>) -> Self {
        self.columns = columns;
        self
    }

    pub fn with_id_column(mut self, id_column: impl Into<String>) -> Self {
        self.id_column = Some(id_column.into());
        self
    }
}

impl Reference {
    pub fn new(
        name: impl Into<String>,
        target: impl Into<String>,
        kind: ReferenceKind,
        foreign_key: impl Into<String>,
    ) -> Self {
        Self {
            name: name.into(),
            target: target.into(),
            kind,
            foreign_key: foreign_key.into(),
        }
    }
}

/// Cardinality of a relation. Cross-persistence-ness is no longer
/// encoded here — it's determined at resolution time by whether the
/// target Vista lives in the same driver or a different one (the
/// inventory loader knows). YAML specs that previously used
/// `kind: has_foreign` migrate to `kind: has_one` or `kind: has_many`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReferenceKind {
    #[default]
    HasOne,
    HasMany,
}

/// Cardinality of a **contained** relation — records embedded in a column of
/// the parent row. Kept separate from [`ReferenceKind`] (which names
/// foreign-key references) so contained relations don't leak into the
/// foreign-key code paths: a contained relation is not a join, it's a view
/// onto one column.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContainedKind {
    /// One record embedded as an object in the host column (e.g. a product's
    /// `inventory`).
    #[default]
    ContainsOne,
    /// Many records embedded as an array in the host column (e.g. an order's
    /// `lines`).
    ContainsMany,
}