Skip to main content

ferriorm_core/
schema.rs

1//! Validated and resolved Schema IR (Intermediate Representation).
2//!
3//! This is the **single source of truth** consumed by codegen and the migration
4//! engine. It is produced by the validator from the raw [`crate::ast`] AST.
5//!
6//! Key differences from the raw AST:
7//! - Type names are resolved to [`FieldKind::Scalar`], [`FieldKind::Enum`],
8//!   or [`FieldKind::Model`].
9//! - Table and column names are inferred (snake_case + plural) or taken from
10//!   `@@map` / `@map` attributes.
11//! - Relations are fully resolved with cardinality and referential actions.
12//! - Primary keys, indexes, and unique constraints are normalized.
13//!
14//! All types in this module support optional `serde` serialization (behind the
15//! `serde` feature flag) for JSON schema snapshots used by the migration engine.
16
17use crate::ast::{DefaultValue, ReferentialAction};
18use crate::types::{DatabaseProvider, ScalarType};
19
20macro_rules! serde_derive {
21    ($(#[$meta:meta])* $vis:vis struct $name:ident { $($body:tt)* }) => {
22        $(#[$meta])*
23        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24        $vis struct $name { $($body)* }
25    };
26    ($(#[$meta:meta])* $vis:vis enum $name:ident { $($body:tt)* }) => {
27        $(#[$meta])*
28        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29        $vis enum $name { $($body)* }
30    };
31}
32
33serde_derive! {
34    /// A fully validated schema.
35    #[derive(Debug, Clone)]
36    pub struct Schema {
37        pub datasource: DatasourceConfig,
38        pub generators: Vec<GeneratorConfig>,
39        pub enums: Vec<Enum>,
40        pub models: Vec<Model>,
41    }
42}
43
44serde_derive! {
45    /// Resolved datasource configuration.
46    #[derive(Debug, Clone)]
47    pub struct DatasourceConfig {
48        pub name: String,
49        pub provider: DatabaseProvider,
50        pub url: String,
51    }
52}
53
54serde_derive! {
55    /// Resolved generator configuration.
56    #[derive(Debug, Clone)]
57    pub struct GeneratorConfig {
58        pub name: String,
59        pub output: String,
60    }
61}
62
63serde_derive! {
64    /// A validated enum definition.
65    #[derive(Debug, Clone)]
66    pub struct Enum {
67        pub name: String,
68        pub db_name: String,
69        pub variants: Vec<String>,
70    }
71}
72
73serde_derive! {
74    /// A validated model definition.
75    #[derive(Debug, Clone)]
76    pub struct Model {
77        pub name: String,
78        pub db_name: String,
79        pub fields: Vec<Field>,
80        pub primary_key: PrimaryKey,
81        pub indexes: Vec<Index>,
82        pub unique_constraints: Vec<UniqueConstraint>,
83    }
84}
85
86serde_derive! {
87    /// A validated field definition.
88    #[derive(Debug, Clone)]
89    pub struct Field {
90        pub name: String,
91        pub db_name: String,
92        pub field_type: FieldKind,
93        pub is_optional: bool,
94        pub is_list: bool,
95        pub is_id: bool,
96        pub is_unique: bool,
97        pub is_updated_at: bool,
98        pub default: Option<DefaultValue>,
99        pub relation: Option<ResolvedRelation>,
100    }
101}
102
103impl Field {
104    /// Returns true if this field is a scalar (stored in the database), not a relation.
105    pub fn is_scalar(&self) -> bool {
106        !matches!(self.field_type, FieldKind::Model(_)) && !self.is_list
107    }
108
109    /// Returns true if this field has a server-side default and can be omitted on create.
110    pub fn has_default(&self) -> bool {
111        self.default.is_some() || self.is_updated_at
112    }
113}
114
115serde_derive! {
116    /// The kind of a field's type.
117    #[derive(Debug, Clone, PartialEq, Eq)]
118    pub enum FieldKind {
119        /// A scalar type (String, Int, etc.)
120        Scalar(ScalarType),
121        /// A reference to an enum defined in the schema.
122        Enum(String),
123        /// A relation to another model.
124        Model(String),
125    }
126}
127
128serde_derive! {
129    /// A resolved relation between two models.
130    #[derive(Debug, Clone)]
131    pub struct ResolvedRelation {
132        pub related_model: String,
133        pub relation_type: RelationType,
134        pub fields: Vec<String>,
135        pub references: Vec<String>,
136        pub on_delete: ReferentialAction,
137        pub on_update: ReferentialAction,
138    }
139}
140
141serde_derive! {
142    /// The cardinality of a relation.
143    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
144    pub enum RelationType {
145        OneToOne,
146        OneToMany,
147        ManyToOne,
148        ManyToMany,
149    }
150}
151
152serde_derive! {
153    /// Primary key definition.
154    #[derive(Debug, Clone)]
155    pub struct PrimaryKey {
156        pub fields: Vec<String>,
157    }
158}
159
160impl PrimaryKey {
161    pub fn is_composite(&self) -> bool {
162        self.fields.len() > 1
163    }
164}
165
166serde_derive! {
167    /// An index definition.
168    #[derive(Debug, Clone)]
169    pub struct Index {
170        pub fields: Vec<String>,
171    }
172}
173
174serde_derive! {
175    /// A unique constraint.
176    #[derive(Debug, Clone)]
177    pub struct UniqueConstraint {
178        pub fields: Vec<String>,
179    }
180}