rorm_declaration/
imr.rs

1//! The Internal Model Representation used by our migration cli tool
2use std::collections::hash_map::DefaultHasher;
3use std::fmt::{Display, Formatter};
4use std::hash::{Hash, Hasher};
5
6use ordered_float::OrderedFloat;
7use serde::{Deserialize, Serialize};
8use strum::EnumIter;
9
10/// A collection of all models used in the resulting application
11#[derive(Serialize, Deserialize, Debug, Clone, Hash)]
12#[serde(rename_all = "PascalCase")]
13pub struct InternalModelFormat {
14    /// List of all models
15    pub models: Vec<Model>,
16}
17
18/// A single model i.e. database table
19#[derive(Serialize, Deserialize, Debug, Clone)]
20#[serde(rename_all = "PascalCase")]
21pub struct Model {
22    /// Name of the table
23    pub name: String,
24
25    /// List of columns of the table
26    pub fields: Vec<Field>,
27
28    /// Optional source reference to enhance error messages
29    #[serde(default)]
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub source_defined_at: Option<Source>,
32}
33
34impl PartialEq for Model {
35    fn eq(&self, other: &Self) -> bool {
36        self.name == other.name && self.fields == other.fields
37    }
38}
39
40impl Hash for Model {
41    fn hash<H: Hasher>(&self, state: &mut H) {
42        self.fields.hash(state);
43        self.name.hash(state);
44    }
45
46    fn hash_slice<H: Hasher>(data: &[Self], state: &mut H)
47    where
48        Self: Sized,
49    {
50        data.iter().for_each(|x| x.hash(state));
51    }
52}
53
54/// Model's fields i.e. the table's columns
55#[derive(Serialize, Deserialize, Debug, Clone)]
56#[serde(rename_all = "PascalCase")]
57pub struct Field {
58    /// Name of the column
59    pub name: String,
60
61    /// Type of the column
62    #[serde(rename = "Type")]
63    pub db_type: DbType,
64
65    /// List of annotations, constraints, etc.
66    pub annotations: Vec<Annotation>,
67
68    /// Optional source reference to enhance error messages
69    #[serde(default)]
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub source_defined_at: Option<Source>,
72}
73
74impl PartialEq for Field {
75    fn eq(&self, other: &Self) -> bool {
76        self.name == other.name
77            && self.db_type == other.db_type
78            && self.annotations == other.annotations
79    }
80}
81
82impl Hash for Field {
83    fn hash<H: Hasher>(&self, state: &mut H) {
84        self.name.hash(state);
85        self.annotations.hash(state);
86        self.db_type.hash(state);
87    }
88
89    fn hash_slice<H: Hasher>(data: &[Self], state: &mut H)
90    where
91        Self: Sized,
92    {
93        data.iter().for_each(|x| x.hash(state));
94    }
95}
96
97/// Location in the source code a [Model] or [Field] originates from
98/// Used for better error messages in the migration tool
99#[derive(Serialize, Deserialize, Debug, Clone, Hash)]
100#[serde(rename_all = "PascalCase")]
101pub struct Source {
102    /// Filename of the source code of the [Model] or [Field]
103    pub file: String,
104    /// Line of the [Model] or [Field]
105    pub line: usize,
106    /// Column of the [Model] or [Field]
107    pub column: usize,
108}
109
110/// All column types supported by the migration tool
111#[allow(missing_docs)]
112#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
113#[serde(rename_all = "lowercase")]
114pub enum DbType {
115    VarChar,
116    Binary,
117    Int8,
118    Int16,
119    Int32,
120    Int64,
121    #[serde(rename = "float_number")]
122    Float,
123    #[serde(rename = "double_number")]
124    Double,
125    Boolean,
126    Date,
127    DateTime,
128    Timestamp,
129    Time,
130    Choices,
131    Uuid,
132    MacAddress,
133    IpNetwork,
134    BitVec,
135}
136
137/// The subset of annotations which need to be communicated with the migration tool
138#[non_exhaustive]
139#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, EnumIter)]
140#[serde(tag = "Type", content = "Value")]
141#[serde(rename_all = "snake_case")]
142pub enum Annotation {
143    /// Only for [DbType::Timestamp], [DbType::DateTime], [DbType::Time] and [DbType::Date].
144    /// Will set the current time of the database when a row is created.
145    AutoCreateTime,
146    /// Only for [DbType::Timestamp], [DbType::DateTime], [DbType::Time] and [DbType::Date].
147    /// Will set the current time of the database when a row is updated.
148    AutoUpdateTime,
149    /// AUTO_INCREMENT constraint
150    AutoIncrement,
151    /// A list of choices to set
152    Choices(Vec<String>),
153    /// DEFAULT constraint
154    DefaultValue(DefaultValue),
155    /// Create an index. The optional [IndexValue] can be used, to build more complex indexes.
156    Index(Option<IndexValue>),
157    /// Only for VARCHAR, VARBINARY. Specifies the maximum length of the column's content.
158    MaxLength(i32),
159    /// NOT NULL constraint
160    NotNull,
161    /// The annotated column will be used as primary key
162    PrimaryKey,
163    /// UNIQUE constraint
164    Unique,
165    /// Foreign Key constraint
166    ForeignKey(ForeignKey),
167}
168
169impl Annotation {
170    /**
171    Alternative shallow equals function.
172
173    Returns true on:
174    ```rust
175    use rorm_declaration::imr::Annotation;
176
177    assert!(Annotation::MaxLength(0).eq_shallow(&Annotation::MaxLength(255)));
178    ```
179    */
180    pub fn eq_shallow(&self, other: &Self) -> bool {
181        match (self, other) {
182            (Annotation::AutoCreateTime, Annotation::AutoCreateTime) => true,
183            (Annotation::AutoCreateTime, _) => false,
184            (Annotation::AutoUpdateTime, Annotation::AutoUpdateTime) => true,
185            (Annotation::AutoUpdateTime, _) => false,
186            (Annotation::AutoIncrement, Annotation::AutoIncrement) => true,
187            (Annotation::AutoIncrement, _) => false,
188            (Annotation::Choices(_), Annotation::Choices(_)) => true,
189            (Annotation::Choices(_), _) => false,
190            (Annotation::DefaultValue(_), Annotation::DefaultValue(_)) => true,
191            (Annotation::DefaultValue(_), _) => false,
192            (Annotation::Index(_), Annotation::Index(_)) => true,
193            (Annotation::Index(_), _) => false,
194            (Annotation::MaxLength(_), Annotation::MaxLength(_)) => true,
195            (Annotation::MaxLength(_), _) => false,
196            (Annotation::NotNull, Annotation::NotNull) => true,
197            (Annotation::NotNull, _) => false,
198            (Annotation::PrimaryKey, Annotation::PrimaryKey) => true,
199            (Annotation::PrimaryKey, _) => false,
200            (Annotation::Unique, Annotation::Unique) => true,
201            (Annotation::Unique, _) => false,
202            (Annotation::ForeignKey(_), Annotation::ForeignKey(_)) => true,
203            (Annotation::ForeignKey(_), _) => false,
204        }
205    }
206
207    /**
208    Alternative shallow hash function.
209
210    Returns true on:
211    ```rust
212    use rorm_declaration::imr::Annotation;
213
214    assert_eq!(Annotation::MaxLength(0).hash_shallow(), Annotation::MaxLength(255).hash_shallow());
215    ```
216    */
217    pub fn hash_shallow(&self) -> u64 {
218        let mut state = DefaultHasher::new();
219        match self {
220            Annotation::AutoCreateTime => state.write_i8(0),
221            Annotation::AutoUpdateTime => state.write_i8(1),
222            Annotation::AutoIncrement => state.write_i8(2),
223            Annotation::Choices(_) => state.write_i8(3),
224            Annotation::DefaultValue(_) => state.write_i8(4),
225            Annotation::Index(_) => state.write_i8(5),
226            Annotation::MaxLength(_) => state.write_i8(6),
227            Annotation::NotNull => state.write_i8(7),
228            Annotation::PrimaryKey => state.write_i8(8),
229            Annotation::Unique => state.write_i8(9),
230            Annotation::ForeignKey(_) => state.write_i8(10),
231        }
232        state.finish()
233    }
234}
235
236#[cfg(test)]
237mod test {
238
239    use crate::imr::{Annotation, IndexValue};
240
241    #[test]
242    fn test_annotation_hash() {
243        assert_eq!(
244            Annotation::MaxLength(1).hash_shallow(),
245            Annotation::MaxLength(12313).hash_shallow()
246        );
247
248        assert_eq!(
249            Annotation::Index(None).hash_shallow(),
250            Annotation::Index(Some(IndexValue {
251                priority: None,
252                name: "foo".to_string(),
253            }))
254            .hash_shallow()
255        );
256    }
257
258    #[test]
259    fn test_annotation_partial_eq() {
260        assert!(Annotation::MaxLength(1).eq_shallow(&Annotation::MaxLength(2)));
261        assert!(
262            Annotation::Index(None).eq_shallow(&Annotation::Index(Some(IndexValue {
263                priority: None,
264                name: "foo".to_string()
265            })))
266        );
267    }
268}
269
270/// Represents a foreign key
271#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq, Default)]
272#[serde(rename_all = "PascalCase")]
273pub struct ForeignKey {
274    /// Name of the table that should be referenced
275    pub table_name: String,
276    /// Name of the column that should be referenced
277    pub column_name: String,
278    /// Action to be used in case of on delete
279    pub on_delete: ReferentialAction,
280    /// Action to be used in case of an update
281    pub on_update: ReferentialAction,
282}
283
284/**
285Action that gets trigger on update and on delete.
286*/
287#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
288#[serde(rename_all = "PascalCase")]
289pub enum ReferentialAction {
290    /// Stop operation if any keys still depend on the parent table
291    Restrict,
292    /// The action is cascaded
293    Cascade,
294    /// The field is set to null
295    SetNull,
296    /// The field is set to its default
297    SetDefault,
298}
299
300impl Default for ReferentialAction {
301    fn default() -> Self {
302        Self::Restrict
303    }
304}
305
306impl Display for ReferentialAction {
307    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
308        match self {
309            ReferentialAction::Restrict => write!(f, "RESTRICT"),
310            ReferentialAction::Cascade => write!(f, "CASCADE"),
311            ReferentialAction::SetNull => write!(f, "SET NULL"),
312            ReferentialAction::SetDefault => write!(f, "SET DEFAULT"),
313        }
314    }
315}
316
317/// Represents a complex index
318#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
319#[serde(rename_all = "PascalCase")]
320pub struct IndexValue {
321    /// Name of the index. Can be used multiple times in a [Model] to create an
322    /// index with multiple columns.
323    pub name: String,
324
325    /// The order to put the columns in while generating an index.
326    /// Only useful if multiple columns with the same name are present.
327    #[serde(default)]
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub priority: Option<i32>,
330}
331
332/// A column's default value which is any non object / array json value
333#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
334#[serde(untagged)]
335pub enum DefaultValue {
336    /// Use hexadecimal to represent binary data
337    String(String),
338    /// i64 is used as it can represent any integer defined in DbType
339    Integer(i64),
340    /// Ordered float is used as f64 does not Eq and Order which are needed for Hash
341    Float(OrderedFloat<f64>),
342    /// Just a bool. Nothing interesting here.
343    Boolean(bool),
344}
345
346/**
347This implementation exists for strum::EnumIter
348*/
349impl Default for DefaultValue {
350    fn default() -> Self {
351        DefaultValue::Boolean(true)
352    }
353}