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        /// `@db.*` type hint, e.g. `BigInt` for `@db.BigInt`.
101        /// Tuple is `(type_name, args)` matching `FieldAttribute::DbType`.
102        pub db_type: Option<(String, Vec<String>)>,
103    }
104}
105
106impl Field {
107    /// Returns true if this field is a scalar (stored in the database), not a relation.
108    #[must_use]
109    pub fn is_scalar(&self) -> bool {
110        !matches!(self.field_type, FieldKind::Model(_)) && !self.is_list
111    }
112
113    /// Returns true if this field has a server-side default and can be omitted on create.
114    #[must_use]
115    pub fn has_default(&self) -> bool {
116        self.default.is_some() || self.is_updated_at
117    }
118}
119
120serde_derive! {
121    /// The kind of a field's type.
122    #[derive(Debug, Clone, PartialEq, Eq)]
123    pub enum FieldKind {
124        /// A scalar type (String, Int, etc.)
125        Scalar(ScalarType),
126        /// A reference to an enum defined in the schema.
127        Enum(String),
128        /// A relation to another model.
129        Model(String),
130    }
131}
132
133serde_derive! {
134    /// A resolved relation between two models.
135    #[derive(Debug, Clone)]
136    pub struct ResolvedRelation {
137        pub related_model: String,
138        pub relation_type: RelationType,
139        pub fields: Vec<String>,
140        pub references: Vec<String>,
141        pub on_delete: ReferentialAction,
142        pub on_update: ReferentialAction,
143    }
144}
145
146serde_derive! {
147    /// The cardinality of a relation.
148    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
149    pub enum RelationType {
150        OneToOne,
151        OneToMany,
152        ManyToOne,
153        ManyToMany,
154    }
155}
156
157serde_derive! {
158    /// Primary key definition.
159    #[derive(Debug, Clone)]
160    pub struct PrimaryKey {
161        pub fields: Vec<String>,
162    }
163}
164
165impl PrimaryKey {
166    /// Returns true if this is a composite (multi-field) primary key.
167    #[must_use]
168    pub fn is_composite(&self) -> bool {
169        self.fields.len() > 1
170    }
171}
172
173serde_derive! {
174    /// An index definition.
175    #[derive(Debug, Clone)]
176    pub struct Index {
177        pub fields: Vec<String>,
178    }
179}
180
181serde_derive! {
182    /// A unique constraint.
183    #[derive(Debug, Clone)]
184    pub struct UniqueConstraint {
185        pub fields: Vec<String>,
186    }
187}