toasty_core/stmt/
path.rs

1use super::{Expr, Projection};
2use crate::schema::app::{FieldId, ModelId, VariantId};
3
4/// The root of a path traversal.
5///
6/// A path can originate from a top-level model or from a specific variant of
7/// an embedded enum field.
8#[derive(Debug, Clone, PartialEq)]
9pub enum PathRoot {
10    /// The path originates from a top-level model.
11    Model(ModelId),
12
13    /// The path originates from a specific variant of an embedded enum.
14    ///
15    /// `parent` navigates to the enum field; subsequent projection steps index
16    /// into that variant's fields using 0-based local indices.
17    Variant {
18        parent: Box<Path>,
19        variant_id: VariantId,
20    },
21}
22
23impl PathRoot {
24    /// Returns the `ModelId`, panicking if this root is a `Variant` root.
25    pub fn expect_model(&self) -> ModelId {
26        match self {
27            PathRoot::Model(id) => *id,
28            PathRoot::Variant { .. } => panic!("expected Model root, got Variant root"),
29        }
30    }
31
32    /// Returns the `ModelId` if this is a `Model` root, or `None` for a
33    /// `Variant` root.
34    pub fn as_model(&self) -> Option<ModelId> {
35        match self {
36            PathRoot::Model(id) => Some(*id),
37            PathRoot::Variant { .. } => None,
38        }
39    }
40}
41
42/// Describes a traversal through fields.
43///
44/// The root is not specified as part of the struct.
45#[derive(Debug, Clone, PartialEq)]
46pub struct Path {
47    /// Where the path originates from
48    pub root: PathRoot,
49
50    /// Traversal through the fields
51    pub projection: Projection,
52}
53
54impl Path {
55    pub fn model(root: impl Into<ModelId>) -> Self {
56        Self {
57            root: PathRoot::Model(root.into()),
58            projection: Projection::identity(),
59        }
60    }
61
62    pub fn field(root: impl Into<ModelId>, field: usize) -> Self {
63        Self {
64            root: PathRoot::Model(root.into()),
65            projection: Projection::single(field),
66        }
67    }
68
69    pub const fn from_index(root: ModelId, index: usize) -> Self {
70        Self {
71            root: PathRoot::Model(root),
72            projection: Projection::from_index(index),
73        }
74    }
75
76    /// Creates a path rooted at a specific enum variant.
77    ///
78    /// `parent` is the path that navigates to the enum field. Subsequent
79    /// projection steps (appended via [`chain`][Path::chain]) index into the
80    /// variant's fields using 0-based local indices.
81    pub fn from_variant(parent: Path, variant_id: VariantId) -> Self {
82        Self {
83            root: PathRoot::Variant {
84                parent: Box::new(parent),
85                variant_id,
86            },
87            projection: Projection::identity(),
88        }
89    }
90
91    pub fn is_empty(&self) -> bool {
92        self.projection.is_empty()
93    }
94
95    pub fn len(&self) -> usize {
96        self.projection.len()
97    }
98
99    pub fn chain(&mut self, other: &Self) {
100        for field in &other.projection[..] {
101            self.projection.push(*field);
102        }
103    }
104
105    pub fn into_stmt(self) -> Expr {
106        match self.root {
107            PathRoot::Model(model_id) => match self.projection.as_slice() {
108                [] => Expr::ref_ancestor_model(0),
109                [field, project @ ..] => {
110                    let mut ret = Expr::ref_self_field(FieldId {
111                        model: model_id,
112                        index: *field,
113                    });
114
115                    if !project.is_empty() {
116                        ret = Expr::project(ret, project);
117                    }
118
119                    ret
120                }
121            },
122            PathRoot::Variant { parent, .. } => {
123                let parent_expr = parent.into_stmt();
124                match self.projection.as_slice() {
125                    [] => parent_expr,
126                    [local_idx, rest @ ..] => {
127                        // Record position 0 is the discriminant; variant fields
128                        // start at position 1, so add 1 to the local field index.
129                        let mut ret = Expr::project(parent_expr, Projection::single(local_idx + 1));
130
131                        if !rest.is_empty() {
132                            ret = Expr::project(ret, rest);
133                        }
134
135                        ret
136                    }
137                }
138            }
139        }
140    }
141}