Skip to main content

toasty_core/schema/app/
model.rs

1use super::{Field, FieldId, FieldPrimitive, Index, Name, PrimaryKey};
2use crate::{Result, driver, stmt};
3use std::fmt;
4
5/// A model in the application schema.
6///
7/// Models come in three flavors:
8///
9/// - [`Model::Root`] -- a top-level model backed by its own database table.
10/// - [`Model::EmbeddedStruct`] -- a struct whose fields are flattened into a
11///   parent model's table.
12/// - [`Model::EmbeddedEnum`] -- an enum stored via a discriminant column plus
13///   optional per-variant data columns in the parent table.
14///
15/// # Examples
16///
17/// ```ignore
18/// use toasty_core::schema::app::{Model, Schema};
19///
20/// let schema: Schema = /* built from derive macros */;
21/// for model in schema.models() {
22///     if model.is_root() {
23///         println!("Root model: {}", model.name().upper_camel_case());
24///     }
25/// }
26/// ```
27#[derive(Debug, Clone)]
28pub enum Model {
29    /// A root model that maps to its own database table and can be queried
30    /// directly.
31    Root(ModelRoot),
32    /// An embedded struct whose fields are flattened into its parent model's
33    /// table.
34    EmbeddedStruct(EmbeddedStruct),
35    /// An embedded enum stored as a discriminant column (plus optional
36    /// per-variant data columns) in the parent table.
37    EmbeddedEnum(EmbeddedEnum),
38}
39
40/// An ordered collection of [`Model`] definitions.
41///
42/// `ModelSet` is the primary container used to hold all models in a schema.
43/// Models are stored in insertion order and can be iterated over by reference
44/// or by value.
45///
46/// # Examples
47///
48/// ```
49/// use toasty_core::schema::app::{Model, ModelSet};
50///
51/// let mut set = ModelSet::new();
52/// assert_eq!(set.iter().len(), 0);
53/// ```
54#[derive(Debug, Clone, Default)]
55pub struct ModelSet {
56    models: Vec<Model>,
57}
58
59impl ModelSet {
60    /// Creates an empty `ModelSet`.
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Returns the number of models in the set.
66    pub fn len(&self) -> usize {
67        self.models.len()
68    }
69
70    /// Returns `true` if the set contains no models.
71    pub fn is_empty(&self) -> bool {
72        self.models.is_empty()
73    }
74
75    /// Appends a model to the end of the set.
76    pub fn add(&mut self, model: Model) {
77        self.models.push(model);
78    }
79
80    /// Returns an iterator over the models in insertion order.
81    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Model> {
82        self.models.iter()
83    }
84}
85
86impl<'a> IntoIterator for &'a ModelSet {
87    type Item = &'a Model;
88    type IntoIter = std::slice::Iter<'a, Model>;
89
90    fn into_iter(self) -> Self::IntoIter {
91        self.models.iter()
92    }
93}
94
95impl IntoIterator for ModelSet {
96    type Item = Model;
97    type IntoIter = std::vec::IntoIter<Model>;
98
99    fn into_iter(self) -> Self::IntoIter {
100        self.models.into_iter()
101    }
102}
103
104/// A root model backed by its own database table.
105///
106/// Root models have a primary key, may define indices, and are the only model
107/// kind that can be the target of relations. They are the main entities users
108/// interact with through Toasty's query API.
109///
110/// # Examples
111///
112/// ```ignore
113/// let root = model.as_root_unwrap();
114/// let pk_fields: Vec<_> = root.primary_key_fields().collect();
115/// ```
116#[derive(Debug, Clone)]
117pub struct ModelRoot {
118    /// Uniquely identifies this model within the schema.
119    pub id: ModelId,
120
121    /// The model's name.
122    pub name: Name,
123
124    /// All fields defined on this model.
125    pub fields: Vec<Field>,
126
127    /// The primary key definition. Root models always have a primary key.
128    pub primary_key: PrimaryKey,
129
130    /// Optional explicit table name. When `None`, a name is derived from the
131    /// model name.
132    pub table_name: Option<String>,
133
134    /// Secondary indices defined on this model.
135    pub indices: Vec<Index>,
136}
137
138impl ModelRoot {
139    /// Builds a `SELECT` query that filters by this model's primary key using
140    /// the supplied `input` to resolve argument values.
141    pub fn find_by_id(&self, mut input: impl stmt::Input) -> stmt::Query {
142        let filter = match &self.primary_key.fields[..] {
143            [pk_field] => stmt::Expr::eq(
144                stmt::Expr::ref_self_field(pk_field),
145                input
146                    .resolve_arg(&0.into(), &stmt::Projection::identity())
147                    .unwrap(),
148            ),
149            pk_fields => stmt::Expr::and_from_vec(
150                pk_fields
151                    .iter()
152                    .enumerate()
153                    .map(|(i, pk_field)| {
154                        stmt::Expr::eq(
155                            stmt::Expr::ref_self_field(pk_field),
156                            input
157                                .resolve_arg(&i.into(), &stmt::Projection::identity())
158                                .unwrap(),
159                        )
160                    })
161                    .collect(),
162            ),
163        };
164
165        stmt::Query::new_select(self.id, filter)
166    }
167
168    /// Iterate over the fields used for the model's primary key.
169    pub fn primary_key_fields(&self) -> impl ExactSizeIterator<Item = &'_ Field> {
170        self.primary_key
171            .fields
172            .iter()
173            .map(|pk_field| &self.fields[pk_field.index])
174    }
175
176    /// Looks up a field by its application-level name.
177    ///
178    /// Returns `None` if no field with that name exists on this model.
179    pub fn field_by_name(&self, name: &str) -> Option<&Field> {
180        self.fields.iter().find(|field| field.name.app_name == name)
181    }
182
183    pub(crate) fn verify(&self, db: &driver::Capability) -> Result<()> {
184        for field in &self.fields {
185            field.verify(db)?;
186        }
187        Ok(())
188    }
189}
190
191/// An embedded struct model whose fields are flattened into its parent model's
192/// database table.
193///
194/// Embedded structs do not have their own table or primary key. Their fields
195/// become additional columns in the parent table. Indices declared on an
196/// embedded struct's fields are propagated to physical DB indices on the parent
197/// table.
198///
199/// # Examples
200///
201/// ```ignore
202/// let embedded = model.as_embedded_struct_unwrap();
203/// for field in &embedded.fields {
204///     println!("  embedded field: {}", field.name.app_name);
205/// }
206/// ```
207#[derive(Debug, Clone)]
208pub struct EmbeddedStruct {
209    /// Uniquely identifies this model within the schema.
210    pub id: ModelId,
211
212    /// The model's name.
213    pub name: Name,
214
215    /// Fields contained by this embedded struct.
216    pub fields: Vec<Field>,
217
218    /// Indices defined on this embedded struct's fields.
219    ///
220    /// These reference fields within this embedded struct (not the parent
221    /// model). The schema builder propagates them to physical DB indices on
222    /// the parent table's flattened columns.
223    pub indices: Vec<Index>,
224}
225
226impl EmbeddedStruct {
227    pub(crate) fn verify(&self, db: &driver::Capability) -> Result<()> {
228        for field in &self.fields {
229            field.verify(db)?;
230        }
231        Ok(())
232    }
233}
234
235/// An embedded enum model stored in the parent table via a discriminant column
236/// and optional per-variant data columns.
237///
238/// The discriminant column holds a value (integer or string) identifying the active variant.
239/// Variants may optionally carry data fields, which are stored as additional
240/// nullable columns in the parent table.
241///
242/// # Examples
243///
244/// ```ignore
245/// let ee = model.as_embedded_enum_unwrap();
246/// for variant in &ee.variants {
247///     println!("variant {} = {}", variant.name.upper_camel_case(), variant.discriminant);
248/// }
249/// ```
250#[derive(Debug, Clone)]
251pub struct EmbeddedEnum {
252    /// Uniquely identifies this model within the schema.
253    pub id: ModelId,
254
255    /// The model's name.
256    pub name: Name,
257
258    /// The primitive type used for the discriminant column.
259    pub discriminant: FieldPrimitive,
260
261    /// The enum's variants.
262    pub variants: Vec<EnumVariant>,
263
264    /// All fields across all variants, with global indices. Each field's
265    /// [`variant`](Field::variant) identifies which variant it belongs to.
266    pub fields: Vec<Field>,
267
268    /// Indices defined on this embedded enum's variant fields.
269    ///
270    /// These reference fields within this embedded enum (not the parent
271    /// model). The schema builder propagates them to physical DB indices on
272    /// the parent table's flattened columns.
273    pub indices: Vec<Index>,
274}
275
276/// One variant of an [`EmbeddedEnum`].
277///
278/// Each variant has a name and a discriminant value (integer or string) that is
279/// stored in the database to identify which variant is active.
280#[derive(Debug, Clone)]
281pub struct EnumVariant {
282    /// The Rust variant name.
283    pub name: Name,
284
285    /// The discriminant value stored in the database column.
286    /// Typically `Value::I64` for integer discriminants or `Value::String` for
287    /// string discriminants.
288    pub discriminant: stmt::Value,
289}
290
291impl EmbeddedEnum {
292    /// Returns true if at least one variant carries data fields.
293    pub fn has_data_variants(&self) -> bool {
294        !self.fields.is_empty()
295    }
296
297    /// Returns fields belonging to a specific variant.
298    pub fn variant_fields(&self, variant_index: usize) -> impl Iterator<Item = &Field> {
299        let variant_id = VariantId {
300            model: self.id,
301            index: variant_index,
302        };
303        self.fields
304            .iter()
305            .filter(move |f| f.variant == Some(variant_id))
306    }
307
308    pub(crate) fn verify(&self, db: &driver::Capability) -> Result<()> {
309        for field in &self.fields {
310            field.verify(db)?;
311        }
312        Ok(())
313    }
314}
315
316/// Uniquely identifies a [`Model`] within a [`Schema`](super::Schema).
317///
318/// `ModelId` wraps a `usize` index into the schema's model map. It is `Copy`
319/// and can be used as a key for lookups.
320///
321/// # Examples
322///
323/// ```
324/// use toasty_core::schema::app::ModelId;
325///
326/// let id = ModelId(0);
327/// let field_id = id.field(2);
328/// assert_eq!(field_id.model, id);
329/// assert_eq!(field_id.index, 2);
330/// ```
331#[derive(Copy, Clone, Eq, PartialEq, Hash)]
332#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
333pub struct ModelId(pub usize);
334
335impl Model {
336    /// Returns this model's [`ModelId`].
337    pub fn id(&self) -> ModelId {
338        match self {
339            Model::Root(root) => root.id,
340            Model::EmbeddedStruct(embedded) => embedded.id,
341            Model::EmbeddedEnum(e) => e.id,
342        }
343    }
344
345    /// Returns a reference to this model's [`Name`].
346    pub fn name(&self) -> &Name {
347        match self {
348            Model::Root(root) => &root.name,
349            Model::EmbeddedStruct(embedded) => &embedded.name,
350            Model::EmbeddedEnum(e) => &e.name,
351        }
352    }
353
354    /// Returns true if this is a root model (has a table and primary key)
355    pub fn is_root(&self) -> bool {
356        matches!(self, Model::Root(_))
357    }
358
359    /// Returns true if this is an embedded model (flattened into parent)
360    pub fn is_embedded(&self) -> bool {
361        matches!(self, Model::EmbeddedStruct(_) | Model::EmbeddedEnum(_))
362    }
363
364    /// Returns true if this model can be the target of a relation
365    pub fn can_be_relation_target(&self) -> bool {
366        self.is_root()
367    }
368
369    /// Returns the inner [`ModelRoot`] if this is a root model.
370    pub fn as_root(&self) -> Option<&ModelRoot> {
371        match self {
372            Model::Root(root) => Some(root),
373            _ => None,
374        }
375    }
376
377    /// Returns a reference to the root model data.
378    ///
379    /// # Panics
380    ///
381    /// Panics if this is not a [`Model::Root`].
382    pub fn as_root_unwrap(&self) -> &ModelRoot {
383        match self {
384            Model::Root(root) => root,
385            Model::EmbeddedStruct(_) => panic!("expected root model, found embedded struct"),
386            Model::EmbeddedEnum(_) => panic!("expected root model, found embedded enum"),
387        }
388    }
389
390    /// Returns a mutable reference to the root model data.
391    ///
392    /// # Panics
393    ///
394    /// Panics if this is not a [`Model::Root`].
395    pub fn as_root_mut_unwrap(&mut self) -> &mut ModelRoot {
396        match self {
397            Model::Root(root) => root,
398            Model::EmbeddedStruct(_) => panic!("expected root model, found embedded struct"),
399            Model::EmbeddedEnum(_) => panic!("expected root model, found embedded enum"),
400        }
401    }
402
403    /// Returns a reference to the embedded struct data.
404    ///
405    /// # Panics
406    ///
407    /// Panics if this is not a [`Model::EmbeddedStruct`].
408    pub fn as_embedded_struct_unwrap(&self) -> &EmbeddedStruct {
409        match self {
410            Model::EmbeddedStruct(embedded) => embedded,
411            Model::Root(_) => panic!("expected embedded struct, found root model"),
412            Model::EmbeddedEnum(_) => panic!("expected embedded struct, found embedded enum"),
413        }
414    }
415
416    /// Returns a reference to the embedded enum data.
417    ///
418    /// # Panics
419    ///
420    /// Panics if this is not a [`Model::EmbeddedEnum`].
421    pub fn as_embedded_enum_unwrap(&self) -> &EmbeddedEnum {
422        match self {
423            Model::EmbeddedEnum(e) => e,
424            Model::Root(_) => panic!("expected embedded enum, found root model"),
425            Model::EmbeddedStruct(_) => panic!("expected embedded enum, found embedded struct"),
426        }
427    }
428
429    pub(crate) fn verify(&self, db: &driver::Capability) -> Result<()> {
430        match self {
431            Model::Root(root) => root.verify(db),
432            Model::EmbeddedStruct(embedded) => embedded.verify(db),
433            Model::EmbeddedEnum(e) => e.verify(db),
434        }
435    }
436}
437
438/// Identifies a specific variant within an [`EmbeddedEnum`] model.
439///
440/// # Examples
441///
442/// ```
443/// use toasty_core::schema::app::ModelId;
444///
445/// let variant_id = ModelId(1).variant(0);
446/// assert_eq!(variant_id.model, ModelId(1));
447/// assert_eq!(variant_id.index, 0);
448/// ```
449#[derive(Copy, Clone, PartialEq, Eq, Hash)]
450pub struct VariantId {
451    /// The enum model this variant belongs to.
452    pub model: ModelId,
453    /// Index of the variant within `EmbeddedEnum::variants`.
454    pub index: usize,
455}
456
457impl fmt::Debug for VariantId {
458    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
459        write!(fmt, "VariantId({}/{})", self.model.0, self.index)
460    }
461}
462
463impl ModelId {
464    /// Create a `FieldId` representing the current model's field at index
465    /// `index`.
466    pub const fn field(self, index: usize) -> FieldId {
467        FieldId { model: self, index }
468    }
469
470    /// Create a `VariantId` representing the current model's variant at
471    /// `index`.
472    pub const fn variant(self, index: usize) -> VariantId {
473        VariantId { model: self, index }
474    }
475
476    pub(crate) const fn placeholder() -> Self {
477        Self(usize::MAX)
478    }
479}
480
481impl From<&Self> for ModelId {
482    fn from(src: &Self) -> Self {
483        *src
484    }
485}
486
487impl From<&mut Self> for ModelId {
488    fn from(src: &mut Self) -> Self {
489        *src
490    }
491}
492
493impl From<&Model> for ModelId {
494    fn from(value: &Model) -> Self {
495        value.id()
496    }
497}
498
499impl From<&ModelRoot> for ModelId {
500    fn from(value: &ModelRoot) -> Self {
501        value.id
502    }
503}
504
505impl fmt::Debug for ModelId {
506    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
507        write!(fmt, "ModelId({})", self.0)
508    }
509}