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