toasty-core 0.11.0

Core types, schema representations, and driver interface for Toasty
Documentation
use super::{Expr, Projection};
use crate::schema::app::{FieldId, ModelId, VariantId};

/// The root of a path traversal.
///
/// A path can originate from a top-level model or from a specific variant of
/// an embedded enum field.
#[derive(Debug, Clone, PartialEq)]
pub enum PathRoot {
    /// The path originates from a top-level model.
    Model(ModelId),

    /// The path originates from a specific variant of an embedded enum.
    ///
    /// `parent` navigates to the enum field; subsequent projection steps index
    /// into that variant's fields using 0-based local indices.
    Variant {
        /// Path that navigates to the enum field containing this variant.
        parent: Box<Path>,
        /// Identifies which variant of the enum this path targets.
        variant_id: VariantId,
    },
}

impl PathRoot {
    /// Returns `true` if this root is an embedded enum variant.
    pub fn is_variant(&self) -> bool {
        matches!(self, Self::Variant { .. })
    }

    /// Returns the `ModelId`, panicking if this root is a `Variant` root.
    pub fn as_model_unwrap(&self) -> ModelId {
        match self {
            PathRoot::Model(id) => *id,
            PathRoot::Variant { .. } => panic!("expected Model root, got Variant root"),
        }
    }

    /// Returns the `ModelId` if this is a `Model` root, or `None` for a
    /// `Variant` root.
    pub fn as_model(&self) -> Option<ModelId> {
        match self {
            PathRoot::Model(id) => Some(*id),
            PathRoot::Variant { .. } => None,
        }
    }
}

/// A rooted field traversal path through the application schema.
///
/// A `Path` starts at a [`PathRoot`] (a model or an enum variant) and
/// navigates through fields via a [`Projection`]. It is used by the query
/// engine to identify which field or nested field is being referenced.
///
/// # Examples
///
/// ```ignore
/// use toasty_core::stmt::Path;
/// use toasty_core::schema::app::ModelId;
///
/// // Path pointing to the root of model 0
/// let p = Path::model(ModelId::from_index(0));
/// assert!(p.projection.is_empty()); // no field steps
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct Path {
    /// Where the path originates from.
    pub root: PathRoot,

    /// Traversal through the fields.
    pub projection: Projection,
}

impl Path {
    /// Creates a path rooted at a model with an identity projection (no field steps).
    pub fn model(root: impl Into<ModelId>) -> Self {
        Self {
            root: PathRoot::Model(root.into()),
            projection: Projection::identity(),
        }
    }

    /// Creates a path rooted at a model that navigates to a single field by index.
    pub fn field(root: impl Into<ModelId>, field: usize) -> Self {
        Self {
            root: PathRoot::Model(root.into()),
            projection: Projection::single(field),
        }
    }

    /// Creates a path rooted at a model with a single field step (const-compatible).
    pub const fn from_index(root: ModelId, index: usize) -> Self {
        Self {
            root: PathRoot::Model(root),
            projection: Projection::from_index(index),
        }
    }

    /// Creates a path rooted at a specific enum variant.
    ///
    /// `parent` is the path that navigates to the enum field. Subsequent
    /// projection steps (appended via [`chain`][Path::chain]) index into the
    /// variant's fields using 0-based local indices. [`into_stmt`] renders
    /// the root as a variant selection ([`Expr::variant`]) over the parent's
    /// expression.
    ///
    /// [`into_stmt`]: Path::into_stmt
    pub fn from_variant(parent: Path, variant_id: VariantId) -> Self {
        Self {
            root: PathRoot::Variant {
                parent: Box::new(parent),
                variant_id,
            },
            projection: Projection::identity(),
        }
    }

    /// Appends `other`'s traversal onto this path.
    ///
    /// `other`'s root model is dropped: its steps continue from wherever
    /// `self` ends. A variant selection in `other` is preserved — the result
    /// selects the same variant at the same point of the traversal — so a
    /// variant-rooted path can be chained onto a path reaching its enum from
    /// another model.
    pub fn chain(&mut self, other: &Self) {
        if let PathRoot::Variant { parent, variant_id } = &other.root {
            self.chain(parent);
            *self = Self::from_variant(self.clone(), *variant_id);
        }

        for field in &other.projection[..] {
            self.projection.push(*field);
        }
    }

    /// Converts this path into an [`Expr`] that references the path's field.
    ///
    /// A variant root becomes an [`ExprVariant`](super::ExprVariant) over the
    /// parent path's expression, projected by the variant-local steps. The
    /// expression carries no variant check; the engine's statement
    /// normalization adds one per selection to the predicate built over it.
    pub fn into_stmt(self) -> Expr {
        self.into_stmt_with_nesting(0)
    }

    /// Converts this path into an [`Expr`] at the specified query nesting level.
    ///
    /// A nesting level of `0` references the current query; `1` references its
    /// parent. Projections and variant selections preserve this nesting level.
    pub fn into_stmt_with_nesting(self, nesting: usize) -> Expr {
        match self.root {
            PathRoot::Model(model_id) => match self.projection.as_slice() {
                [] => Expr::ref_ancestor_model(nesting),
                [field, project @ ..] => {
                    let mut ret = Expr::ref_field(
                        nesting,
                        FieldId {
                            model: model_id,
                            index: *field,
                        },
                    );

                    if !project.is_empty() {
                        ret = Expr::project(ret, project);
                    }

                    ret
                }
            },
            PathRoot::Variant { parent, variant_id } => {
                // The selection stands for the variant's payload, so the
                // steps index the variant's fields by their local positions.
                let selection = Expr::variant(parent.into_stmt_with_nesting(nesting), variant_id);

                match self.projection.as_slice() {
                    [] => selection,
                    steps => Expr::project(selection, steps),
                }
            }
        }
    }
}