vespertide_core/schema/
column.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::schema::{
5    foreign_key::ForeignKeySyntax, names::ColumnName, primary_key::PrimaryKeySyntax,
6    str_or_bool::StrOrBoolOrArray,
7};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
10#[serde(rename_all = "snake_case")]
11pub struct ColumnDef {
12    pub name: ColumnName,
13    pub r#type: ColumnType,
14    pub nullable: bool,
15    pub default: Option<String>,
16    pub comment: Option<String>,
17    pub primary_key: Option<PrimaryKeySyntax>,
18    pub unique: Option<StrOrBoolOrArray>,
19    pub index: Option<StrOrBoolOrArray>,
20    pub foreign_key: Option<ForeignKeySyntax>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24#[serde(rename_all = "snake_case", untagged)]
25pub enum ColumnType {
26    Simple(SimpleColumnType),
27    Complex(ComplexColumnType),
28}
29
30impl ColumnType {
31    /// Convert column type to Rust type string (for SeaORM entity generation)
32    pub fn to_rust_type(&self, nullable: bool) -> String {
33        let base = match self {
34            ColumnType::Simple(ty) => match ty {
35                SimpleColumnType::SmallInt => "i16".to_string(),
36                SimpleColumnType::Integer => "i32".to_string(),
37                SimpleColumnType::BigInt => "i64".to_string(),
38                SimpleColumnType::Real => "f32".to_string(),
39                SimpleColumnType::DoublePrecision => "f64".to_string(),
40                SimpleColumnType::Text => "String".to_string(),
41                SimpleColumnType::Boolean => "bool".to_string(),
42                SimpleColumnType::Date => "Date".to_string(),
43                SimpleColumnType::Time => "Time".to_string(),
44                SimpleColumnType::Timestamp => "DateTime".to_string(),
45                SimpleColumnType::Timestamptz => "DateTimeWithTimeZone".to_string(),
46                SimpleColumnType::Interval => "String".to_string(),
47                SimpleColumnType::Bytea => "Vec<u8>".to_string(),
48                SimpleColumnType::Uuid => "Uuid".to_string(),
49                SimpleColumnType::Json | SimpleColumnType::Jsonb => "Json".to_string(),
50                SimpleColumnType::Inet | SimpleColumnType::Cidr => "String".to_string(),
51                SimpleColumnType::Macaddr => "String".to_string(),
52                SimpleColumnType::Xml => "String".to_string(),
53            },
54            ColumnType::Complex(ty) => match ty {
55                ComplexColumnType::Varchar { .. } => "String".to_string(),
56                ComplexColumnType::Numeric { .. } => "Decimal".to_string(),
57                ComplexColumnType::Char { .. } => "String".to_string(),
58                ComplexColumnType::Custom { .. } => "String".to_string(), // Default for custom types
59                ComplexColumnType::Enum { .. } => "String".to_string(),
60            },
61        };
62
63        if nullable {
64            format!("Option<{}>", base)
65        } else {
66            base
67        }
68    }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
72#[serde(rename_all = "snake_case")]
73pub enum SimpleColumnType {
74    SmallInt,
75    Integer,
76    BigInt,
77    Real,
78    DoublePrecision,
79
80    // Text types
81    Text,
82
83    // Boolean type
84    Boolean,
85
86    // Date/Time types
87    Date,
88    Time,
89    Timestamp,
90    Timestamptz,
91    Interval,
92
93    // Binary type
94    Bytea,
95
96    // UUID type
97    Uuid,
98
99    // JSON types
100    Json,
101    Jsonb,
102
103    // Network types
104    Inet,
105    Cidr,
106    Macaddr,
107
108    // XML type
109    Xml,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
113#[serde(rename_all = "snake_case", tag = "kind")]
114pub enum ComplexColumnType {
115    Varchar { length: u32 },
116    Numeric { precision: u32, scale: u32 },
117    Char { length: u32 },
118    Custom { custom_type: String },
119    Enum { name: String, values: Vec<String> },
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use rstest::rstest;
126
127    #[rstest]
128    #[case(SimpleColumnType::SmallInt, "i16")]
129    #[case(SimpleColumnType::Integer, "i32")]
130    #[case(SimpleColumnType::BigInt, "i64")]
131    #[case(SimpleColumnType::Real, "f32")]
132    #[case(SimpleColumnType::DoublePrecision, "f64")]
133    #[case(SimpleColumnType::Text, "String")]
134    #[case(SimpleColumnType::Boolean, "bool")]
135    #[case(SimpleColumnType::Date, "Date")]
136    #[case(SimpleColumnType::Time, "Time")]
137    #[case(SimpleColumnType::Timestamp, "DateTime")]
138    #[case(SimpleColumnType::Timestamptz, "DateTimeWithTimeZone")]
139    #[case(SimpleColumnType::Interval, "String")]
140    #[case(SimpleColumnType::Bytea, "Vec<u8>")]
141    #[case(SimpleColumnType::Uuid, "Uuid")]
142    #[case(SimpleColumnType::Json, "Json")]
143    #[case(SimpleColumnType::Jsonb, "Json")]
144    #[case(SimpleColumnType::Inet, "String")]
145    #[case(SimpleColumnType::Cidr, "String")]
146    #[case(SimpleColumnType::Macaddr, "String")]
147    #[case(SimpleColumnType::Xml, "String")]
148    fn test_simple_column_type_to_rust_type_not_nullable(
149        #[case] column_type: SimpleColumnType,
150        #[case] expected: &str,
151    ) {
152        assert_eq!(
153            ColumnType::Simple(column_type).to_rust_type(false),
154            expected
155        );
156    }
157
158    #[rstest]
159    #[case(SimpleColumnType::SmallInt, "Option<i16>")]
160    #[case(SimpleColumnType::Integer, "Option<i32>")]
161    #[case(SimpleColumnType::BigInt, "Option<i64>")]
162    #[case(SimpleColumnType::Real, "Option<f32>")]
163    #[case(SimpleColumnType::DoublePrecision, "Option<f64>")]
164    #[case(SimpleColumnType::Text, "Option<String>")]
165    #[case(SimpleColumnType::Boolean, "Option<bool>")]
166    #[case(SimpleColumnType::Date, "Option<Date>")]
167    #[case(SimpleColumnType::Time, "Option<Time>")]
168    #[case(SimpleColumnType::Timestamp, "Option<DateTime>")]
169    #[case(SimpleColumnType::Timestamptz, "Option<DateTimeWithTimeZone>")]
170    #[case(SimpleColumnType::Interval, "Option<String>")]
171    #[case(SimpleColumnType::Bytea, "Option<Vec<u8>>")]
172    #[case(SimpleColumnType::Uuid, "Option<Uuid>")]
173    #[case(SimpleColumnType::Json, "Option<Json>")]
174    #[case(SimpleColumnType::Jsonb, "Option<Json>")]
175    #[case(SimpleColumnType::Inet, "Option<String>")]
176    #[case(SimpleColumnType::Cidr, "Option<String>")]
177    #[case(SimpleColumnType::Macaddr, "Option<String>")]
178    #[case(SimpleColumnType::Xml, "Option<String>")]
179    fn test_simple_column_type_to_rust_type_nullable(
180        #[case] column_type: SimpleColumnType,
181        #[case] expected: &str,
182    ) {
183        assert_eq!(ColumnType::Simple(column_type).to_rust_type(true), expected);
184    }
185
186    #[rstest]
187    #[case(ComplexColumnType::Varchar { length: 255 }, false, "String")]
188    #[case(ComplexColumnType::Varchar { length: 50 }, false, "String")]
189    #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, false, "Decimal")]
190    #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, false, "Decimal")]
191    #[case(ComplexColumnType::Char { length: 10 }, false, "String")]
192    #[case(ComplexColumnType::Char { length: 1 }, false, "String")]
193    #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, false, "String")]
194    #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, false, "String")]
195    #[case(ComplexColumnType::Enum { name: "status".into(), values: vec!["active".into(), "inactive".into()] }, false, "String")]
196    fn test_complex_column_type_to_rust_type_not_nullable(
197        #[case] column_type: ComplexColumnType,
198        #[case] nullable: bool,
199        #[case] expected: &str,
200    ) {
201        assert_eq!(
202            ColumnType::Complex(column_type).to_rust_type(nullable),
203            expected
204        );
205    }
206
207    #[rstest]
208    #[case(ComplexColumnType::Varchar { length: 255 }, "Option<String>")]
209    #[case(ComplexColumnType::Varchar { length: 50 }, "Option<String>")]
210    #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "Option<Decimal>")]
211    #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, "Option<Decimal>")]
212    #[case(ComplexColumnType::Char { length: 10 }, "Option<String>")]
213    #[case(ComplexColumnType::Char { length: 1 }, "Option<String>")]
214    #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "Option<String>")]
215    #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, "Option<String>")]
216    #[case(ComplexColumnType::Enum { name: "status".into(), values: vec!["active".into(), "inactive".into()] }, "Option<String>")]
217    fn test_complex_column_type_to_rust_type_nullable(
218        #[case] column_type: ComplexColumnType,
219        #[case] expected: &str,
220    ) {
221        assert_eq!(
222            ColumnType::Complex(column_type).to_rust_type(true),
223            expected
224        );
225    }
226}