Skip to main content

toasty_core/stmt/cx/
projected_field.rs

1use super::ExprContext;
2use crate::{
3    schema::{app, mapping},
4    stmt::{Expr, ExprReference, Path},
5};
6
7/// A field resolved from an expression, including its storage mapping and scope.
8#[derive(Debug)]
9pub struct ProjectedField<'a> {
10    /// The application field at the end of the projection.
11    pub field: &'a app::Field,
12
13    /// The field's mapping within the referenced root model.
14    pub mapping: &'a mapping::Field,
15
16    /// The schema path from the referenced root model to the field.
17    ///
18    /// This path carries no query scope. Converting it to an expression with
19    /// [`Path::into_stmt`] produces a current-scope reference; callers must
20    /// preserve [`nesting`](Self::nesting) when reconstructing an outer reference.
21    pub path: Path,
22
23    /// How many query scopes outward the root reference points.
24    ///
25    /// Zero references the current scope, one references its parent, and so on.
26    pub nesting: usize,
27}
28
29impl<'a> ExprContext<'a> {
30    /// Resolves a field reference or embedded-field projection in its query scope.
31    ///
32    /// Walks embedded structs and explicit enum variant selections, preserving
33    /// the root reference's nesting. Relation fields can be returned, but the
34    /// walk does not continue through their target models.
35    ///
36    /// Returns `None` for expressions that do not resolve to a field, invalid
37    /// field or variant projections, and references to non-model scopes.
38    ///
39    /// # Panics
40    ///
41    /// Panics if a field reference's nesting exceeds the available parent scopes.
42    pub fn resolve_projected_field(&self, expr: &Expr) -> Option<ProjectedField<'a>> {
43        match expr {
44            Expr::Reference(ExprReference::Field { nesting, index }) => {
45                let model = self.target_at(*nesting).as_model()?;
46                Some(ProjectedField {
47                    field: model.fields.get(*index)?,
48                    mapping: self.schema.mapping_for(model.id).fields.get(*index)?,
49                    path: Path::field(model.id, *index),
50                    nesting: *nesting,
51                })
52            }
53            Expr::Project(project) => {
54                let projected = if let Expr::Variant(variant) = &*project.base {
55                    let projected = self.resolve_projected_field(&variant.base)?;
56                    let app::FieldTy::Embedded(embedded) = &projected.field.ty else {
57                        return None;
58                    };
59                    if embedded.target != variant.variant.model {
60                        return None;
61                    }
62                    let mapping::Field::Enum(mapping) = projected.mapping else {
63                        return None;
64                    };
65                    let mapping = mapping.variants.get(variant.variant.index)?;
66                    let model = self
67                        .schema
68                        .app
69                        .model(embedded.target)
70                        .as_embedded_enum_unwrap();
71                    let fields = model.variant_fields(variant.variant.index);
72                    let (&index, rest) = project.projection.as_slice().split_first()?;
73                    let mut path = Path::from_variant(projected.path, variant.variant);
74                    path.projection.push(index);
75
76                    return self.resolve_projected_subfield(
77                        ProjectedField {
78                            field: fields.get(index)?,
79                            mapping: mapping.fields.get(index)?,
80                            path,
81                            nesting: projected.nesting,
82                        },
83                        rest,
84                    );
85                } else {
86                    self.resolve_projected_field(&project.base)?
87                };
88                self.resolve_projected_subfield(projected, project.projection.as_slice())
89            }
90            _ => None,
91        }
92    }
93
94    fn resolve_projected_subfield(
95        &self,
96        mut projected: ProjectedField<'a>,
97        steps: &[usize],
98    ) -> Option<ProjectedField<'a>> {
99        for index in steps {
100            let app::FieldTy::Embedded(embedded) = &projected.field.ty else {
101                return None;
102            };
103            let mapping::Field::Struct(mapped) = projected.mapping else {
104                return None;
105            };
106            projected.field = self
107                .schema
108                .app
109                .model(embedded.target)
110                .fields()
111                .get(*index)?;
112            projected.mapping = mapped.fields.get(*index)?;
113            projected.path.projection.push(*index);
114        }
115        Some(projected)
116    }
117}