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