Skip to main content

toasty_core/schema/app/relation/
has.rs

1use crate::{
2    schema::app::{BelongsTo, FieldId, Model, ModelId, Name, Schema},
3    stmt,
4};
5
6/// The inverse side of a relationship.
7///
8/// A `Has` field on model A reaches an associated model B through a paired
9/// [`BelongsTo`] field on B that holds the foreign key. Its cardinality records
10/// whether the field represents "A has many Bs" or "A has one B".
11#[derive(Debug, Clone)]
12pub struct Has {
13    /// The [`ModelId`] of the associated (target) model.
14    pub target: ModelId,
15
16    /// The expression type this field evaluates to from the application's
17    /// perspective.
18    pub expr_ty: stmt::Type,
19
20    /// Whether this relation is one-to-many or one-to-one.
21    pub cardinality: Cardinality,
22
23    /// The paired `BelongsTo` field on the target model.
24    pub pair_id: FieldId,
25}
26
27/// Cardinality for a relation field that reaches another model.
28#[derive(Debug, Clone)]
29pub enum Cardinality {
30    /// The relation yields zero or more associated items.
31    Many {
32        /// The singular name for one associated item (used in generated method
33        /// names).
34        singular: Name,
35    },
36
37    /// The relation yields at most one associated item.
38    One,
39}
40
41impl Has {
42    /// Returns `true` when this is a one-to-many relation.
43    pub fn is_many(&self) -> bool {
44        self.cardinality.is_many()
45    }
46
47    /// Returns `true` when this is a one-to-one relation.
48    pub fn is_one(&self) -> bool {
49        self.cardinality.is_one()
50    }
51
52    /// Returns the singular item name for a one-to-many relation.
53    pub fn singular(&self) -> Option<&Name> {
54        self.cardinality.singular()
55    }
56
57    /// Resolves the target [`Model`] from the given schema.
58    pub fn target<'a>(&self, schema: &'a Schema) -> &'a Model {
59        schema.model(self.target)
60    }
61
62    /// Resolves the paired [`BelongsTo`] relation on the target model.
63    ///
64    /// # Panics
65    ///
66    /// Panics if the paired field is not a `BelongsTo` variant.
67    pub fn pair<'a>(&self, schema: &'a Schema) -> &'a BelongsTo {
68        schema.field(self.pair_id).ty.as_belongs_to_unwrap()
69    }
70}
71
72impl Cardinality {
73    /// Returns `true` when this relation yields zero or more items.
74    pub fn is_many(&self) -> bool {
75        matches!(self, Cardinality::Many { .. })
76    }
77
78    /// Returns `true` when this relation yields at most one item.
79    pub fn is_one(&self) -> bool {
80        matches!(self, Cardinality::One)
81    }
82
83    /// Returns the singular item name for a one-to-many relation.
84    pub fn singular(&self) -> Option<&Name> {
85        match self {
86            Cardinality::Many { singular } => Some(singular),
87            Cardinality::One => None,
88        }
89    }
90}