sea_orm/entity/
active_enum.rs

1use crate::{ColIdx, ColumnDef, DbErr, Iterable, QueryResult, TryFromU64, TryGetError, TryGetable};
2use sea_query::{DynIden, Expr, Nullable, SimpleExpr, Value, ValueType};
3
4/// A Rust representation of enum defined in database.
5///
6/// # Implementations
7///
8/// You can implement [ActiveEnum] manually by hand or use the derive macro [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum).
9///
10/// # Examples
11///
12/// Implementing it manually versus using the derive macro [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum).
13///
14/// > See [DeriveActiveEnum](sea_orm_macros::DeriveActiveEnum) for the full specification of macro attributes.
15///
16/// ```rust
17/// use sea_orm::entity::prelude::*;
18///
19/// // Using the derive macro
20/// #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
21/// #[sea_orm(
22///     rs_type = "String",
23///     db_type = "String(StringLen::N(1))",
24///     enum_name = "category"
25/// )]
26/// pub enum DeriveCategory {
27///     #[sea_orm(string_value = "B")]
28///     Big,
29///     #[sea_orm(string_value = "S")]
30///     Small,
31/// }
32///
33/// // Implementing it manually
34/// #[derive(Debug, PartialEq, EnumIter)]
35/// pub enum Category {
36///     Big,
37///     Small,
38/// }
39///
40/// #[derive(Debug, DeriveIden)]
41/// pub struct CategoryEnum;
42///
43/// impl ActiveEnum for Category {
44///     // The macro attribute `rs_type` is being pasted here
45///     type Value = String;
46///
47///     type ValueVec = Vec<String>;
48///
49///     // Will be atomically generated by `DeriveActiveEnum`
50///     fn name() -> DynIden {
51///         SeaRc::new(CategoryEnum)
52///     }
53///
54///     // Will be atomically generated by `DeriveActiveEnum`
55///     fn to_value(&self) -> Self::Value {
56///         match self {
57///             Self::Big => "B",
58///             Self::Small => "S",
59///         }
60///         .to_owned()
61///     }
62///
63///     // Will be atomically generated by `DeriveActiveEnum`
64///     fn try_from_value(v: &Self::Value) -> Result<Self, DbErr> {
65///         match v.as_ref() {
66///             "B" => Ok(Self::Big),
67///             "S" => Ok(Self::Small),
68///             _ => Err(DbErr::Type(format!(
69///                 "unexpected value for Category enum: {}",
70///                 v
71///             ))),
72///         }
73///     }
74///
75///     fn db_type() -> ColumnDef {
76///         // The macro attribute `db_type` is being pasted here
77///         ColumnType::String(StringLen::N(1)).def()
78///     }
79/// }
80/// ```
81///
82/// Using [ActiveEnum] on Model.
83///
84/// ```
85/// use sea_orm::entity::prelude::*;
86///
87/// // Define the `Category` active enum
88/// #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
89/// #[sea_orm(rs_type = "String", db_type = "String(StringLen::N(1))")]
90/// pub enum Category {
91///     #[sea_orm(string_value = "B")]
92///     Big,
93///     #[sea_orm(string_value = "S")]
94///     Small,
95/// }
96///
97/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
98/// #[sea_orm(table_name = "active_enum")]
99/// pub struct Model {
100///     #[sea_orm(primary_key)]
101///     pub id: i32,
102///     // Represents a db column using `Category` active enum
103///     pub category: Category,
104///     pub category_opt: Option<Category>,
105/// }
106///
107/// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
108/// pub enum Relation {}
109///
110/// impl ActiveModelBehavior for ActiveModel {}
111/// ```
112pub trait ActiveEnum: Sized + Iterable {
113    /// Define the Rust type that each enum variant corresponds.
114    type Value: ActiveEnumValue;
115
116    /// This has no purpose. It will be removed in the next major version.
117    type ValueVec;
118
119    /// Get the name of enum
120    fn name() -> DynIden;
121
122    /// Convert enum variant into the corresponding value.
123    fn to_value(&self) -> Self::Value;
124
125    /// Try to convert the corresponding value into enum variant.
126    fn try_from_value(v: &Self::Value) -> Result<Self, DbErr>;
127
128    /// Get the database column definition of this active enum.
129    fn db_type() -> ColumnDef;
130
131    /// Convert an owned enum variant into the corresponding value.
132    fn into_value(self) -> Self::Value {
133        Self::to_value(&self)
134    }
135
136    /// Construct a enum expression with casting
137    fn as_enum(&self) -> SimpleExpr {
138        Expr::val(Self::to_value(self)).as_enum(Self::name())
139    }
140
141    /// Get the name of all enum variants
142    fn values() -> Vec<Self::Value> {
143        Self::iter().map(Self::into_value).collect()
144    }
145}
146
147/// The Rust Value backing ActiveEnums
148pub trait ActiveEnumValue: Into<Value> + ValueType + Nullable + TryGetable {
149    /// For getting an array of enum. Postgres only
150    fn try_get_vec_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Vec<Self>, TryGetError>;
151}
152
153macro_rules! impl_active_enum_value {
154    ($type:ident) => {
155        impl ActiveEnumValue for $type {
156            fn try_get_vec_by<I: ColIdx>(
157                _res: &QueryResult,
158                _index: I,
159            ) -> Result<Vec<Self>, TryGetError> {
160                panic!("Not supported by `postgres-array`")
161            }
162        }
163    };
164}
165
166macro_rules! impl_active_enum_value_with_pg_array {
167    ($type:ident) => {
168        impl ActiveEnumValue for $type {
169            fn try_get_vec_by<I: ColIdx>(
170                _res: &QueryResult,
171                _index: I,
172            ) -> Result<Vec<Self>, TryGetError> {
173                #[cfg(feature = "postgres-array")]
174                {
175                    <Vec<Self>>::try_get_by(_res, _index)
176                }
177                #[cfg(not(feature = "postgres-array"))]
178                panic!("`postgres-array` is not enabled")
179            }
180        }
181    };
182}
183
184impl_active_enum_value!(u8);
185impl_active_enum_value!(u16);
186impl_active_enum_value!(u32);
187impl_active_enum_value!(u64);
188impl_active_enum_value_with_pg_array!(String);
189impl_active_enum_value_with_pg_array!(i8);
190impl_active_enum_value_with_pg_array!(i16);
191impl_active_enum_value_with_pg_array!(i32);
192impl_active_enum_value_with_pg_array!(i64);
193
194impl<T> TryFromU64 for T
195where
196    T: ActiveEnum,
197{
198    fn try_from_u64(_: u64) -> Result<Self, DbErr> {
199        Err(DbErr::ConvertFromU64(
200            "Fail to construct ActiveEnum from a u64, if your primary key consist of a ActiveEnum field, its auto increment should be set to false."
201        ))
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use crate as sea_orm;
208    use crate::{
209        error::*,
210        sea_query::{SeaRc, StringLen},
211        *,
212    };
213    use pretty_assertions::assert_eq;
214
215    #[test]
216    fn active_enum_string() {
217        #[derive(Debug, PartialEq, Eq, EnumIter)]
218        pub enum Category {
219            Big,
220            Small,
221        }
222
223        #[derive(Debug, DeriveIden)]
224        #[sea_orm(iden = "category")]
225        pub struct CategoryEnum;
226
227        impl ActiveEnum for Category {
228            type Value = String;
229
230            type ValueVec = Vec<String>;
231
232            fn name() -> DynIden {
233                SeaRc::new(CategoryEnum)
234            }
235
236            fn to_value(&self) -> Self::Value {
237                match self {
238                    Self::Big => "B",
239                    Self::Small => "S",
240                }
241                .to_owned()
242            }
243
244            fn try_from_value(v: &Self::Value) -> Result<Self, DbErr> {
245                match v.as_ref() {
246                    "B" => Ok(Self::Big),
247                    "S" => Ok(Self::Small),
248                    _ => Err(type_err(format!("unexpected value for Category enum: {v}"))),
249                }
250            }
251
252            fn db_type() -> ColumnDef {
253                ColumnType::String(StringLen::N(1)).def()
254            }
255        }
256
257        #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
258        #[sea_orm(
259            rs_type = "String",
260            db_type = "String(StringLen::N(1))",
261            enum_name = "category"
262        )]
263        pub enum DeriveCategory {
264            #[sea_orm(string_value = "B")]
265            Big,
266            #[sea_orm(string_value = "S")]
267            Small,
268        }
269
270        assert_eq!(Category::Big.to_value(), "B".to_owned());
271        assert_eq!(Category::Small.to_value(), "S".to_owned());
272        assert_eq!(DeriveCategory::Big.to_value(), "B".to_owned());
273        assert_eq!(DeriveCategory::Small.to_value(), "S".to_owned());
274
275        assert_eq!(
276            Category::try_from_value(&"A".to_owned()).err(),
277            Some(type_err("unexpected value for Category enum: A"))
278        );
279        assert_eq!(
280            Category::try_from_value(&"B".to_owned()).ok(),
281            Some(Category::Big)
282        );
283        assert_eq!(
284            Category::try_from_value(&"S".to_owned()).ok(),
285            Some(Category::Small)
286        );
287        assert_eq!(
288            DeriveCategory::try_from_value(&"A".to_owned()).err(),
289            Some(type_err("unexpected value for DeriveCategory enum: A"))
290        );
291        assert_eq!(
292            DeriveCategory::try_from_value(&"B".to_owned()).ok(),
293            Some(DeriveCategory::Big)
294        );
295        assert_eq!(
296            DeriveCategory::try_from_value(&"S".to_owned()).ok(),
297            Some(DeriveCategory::Small)
298        );
299
300        assert_eq!(
301            Category::db_type(),
302            ColumnType::String(StringLen::N(1)).def()
303        );
304        assert_eq!(
305            DeriveCategory::db_type(),
306            ColumnType::String(StringLen::N(1)).def()
307        );
308
309        assert_eq!(
310            Category::name().to_string(),
311            DeriveCategory::name().to_string()
312        );
313        assert_eq!(Category::values(), DeriveCategory::values());
314
315        assert_eq!(format!("{}", DeriveCategory::Big), "Big");
316        assert_eq!(format!("{}", DeriveCategory::Small), "Small");
317    }
318
319    #[test]
320    fn active_enum_derive_signed_integers() {
321        macro_rules! test_num_value_int {
322            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
323                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
324                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
325                pub enum $ident {
326                    #[sea_orm(num_value = -10)]
327                    Negative,
328                    #[sea_orm(num_value = 1)]
329                    Big,
330                    #[sea_orm(num_value = 0)]
331                    Small,
332                }
333
334                test_int!($ident, $rs_type, $db_type, $col_def);
335            };
336        }
337
338        macro_rules! test_fallback_int {
339            ($ident: ident, $fallback_type: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
340                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
341                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
342                #[repr(i32)]
343                pub enum $ident {
344                    Big = 1,
345                    Small = 0,
346                    Negative = -10,
347                }
348
349                test_int!($ident, $rs_type, $db_type, $col_def);
350            };
351        }
352
353        macro_rules! test_int {
354            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
355                assert_eq!($ident::Big.to_value(), 1);
356                assert_eq!($ident::Small.to_value(), 0);
357                assert_eq!($ident::Negative.to_value(), -10);
358
359                assert_eq!($ident::try_from_value(&1).ok(), Some($ident::Big));
360                assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small));
361                assert_eq!($ident::try_from_value(&-10).ok(), Some($ident::Negative));
362                assert_eq!(
363                    $ident::try_from_value(&2).err(),
364                    Some(type_err(format!(
365                        "unexpected value for {} enum: 2",
366                        stringify!($ident)
367                    )))
368                );
369
370                assert_eq!($ident::db_type(), ColumnType::$col_def.def());
371
372                assert_eq!(format!("{}", $ident::Big), "Big");
373                assert_eq!(format!("{}", $ident::Small), "Small");
374                assert_eq!(format!("{}", $ident::Negative), "Negative");
375            };
376        }
377
378        test_num_value_int!(I8, "i8", "TinyInteger", TinyInteger);
379        test_num_value_int!(I16, "i16", "SmallInteger", SmallInteger);
380        test_num_value_int!(I32, "i32", "Integer", Integer);
381        test_num_value_int!(I64, "i64", "BigInteger", BigInteger);
382
383        test_fallback_int!(I8Fallback, i8, "i8", "TinyInteger", TinyInteger);
384        test_fallback_int!(I16Fallback, i16, "i16", "SmallInteger", SmallInteger);
385        test_fallback_int!(I32Fallback, i32, "i32", "Integer", Integer);
386        test_fallback_int!(I64Fallback, i64, "i64", "BigInteger", BigInteger);
387    }
388
389    #[test]
390    fn active_enum_derive_unsigned_integers() {
391        macro_rules! test_num_value_uint {
392            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
393                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
394                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
395                pub enum $ident {
396                    #[sea_orm(num_value = 1)]
397                    Big,
398                    #[sea_orm(num_value = 0)]
399                    Small,
400                }
401
402                test_uint!($ident, $rs_type, $db_type, $col_def);
403            };
404        }
405
406        macro_rules! test_fallback_uint {
407            ($ident: ident, $fallback_type: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
408                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
409                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
410                #[repr($fallback_type)]
411                pub enum $ident {
412                    Big = 1,
413                    Small = 0,
414                }
415
416                test_uint!($ident, $rs_type, $db_type, $col_def);
417            };
418        }
419
420        macro_rules! test_uint {
421            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
422                assert_eq!($ident::Big.to_value(), 1);
423                assert_eq!($ident::Small.to_value(), 0);
424
425                assert_eq!($ident::try_from_value(&1).ok(), Some($ident::Big));
426                assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small));
427                assert_eq!(
428                    $ident::try_from_value(&2).err(),
429                    Some(type_err(format!(
430                        "unexpected value for {} enum: 2",
431                        stringify!($ident)
432                    )))
433                );
434
435                assert_eq!($ident::db_type(), ColumnType::$col_def.def());
436
437                assert_eq!(format!("{}", $ident::Big), "Big");
438                assert_eq!(format!("{}", $ident::Small), "Small");
439            };
440        }
441
442        test_num_value_uint!(U8, "u8", "TinyInteger", TinyInteger);
443        test_num_value_uint!(U16, "u16", "SmallInteger", SmallInteger);
444        test_num_value_uint!(U32, "u32", "Integer", Integer);
445        test_num_value_uint!(U64, "u64", "BigInteger", BigInteger);
446
447        test_fallback_uint!(U8Fallback, u8, "u8", "TinyInteger", TinyInteger);
448        test_fallback_uint!(U16Fallback, u16, "u16", "SmallInteger", SmallInteger);
449        test_fallback_uint!(U32Fallback, u32, "u32", "Integer", Integer);
450        test_fallback_uint!(U64Fallback, u64, "u64", "BigInteger", BigInteger);
451    }
452
453    #[test]
454    fn escaped_non_uax31() {
455        #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)]
456        #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "pop_os_names_typos")]
457        pub enum PopOSTypos {
458            #[sea_orm(string_value = "Pop!_OS")]
459            PopOSCorrect,
460            #[sea_orm(string_value = "Pop\u{2757}_OS")]
461            PopOSEmoji,
462            #[sea_orm(string_value = "Pop!_操作系统")]
463            PopOSChinese,
464            #[sea_orm(string_value = "PopOS")]
465            PopOSASCIIOnly,
466            #[sea_orm(string_value = "Pop OS")]
467            PopOSASCIIOnlyWithSpace,
468            #[sea_orm(string_value = "Pop!OS")]
469            PopOSNoUnderscore,
470            #[sea_orm(string_value = "Pop_OS")]
471            PopOSNoExclaimation,
472            #[sea_orm(string_value = "!PopOS_")]
473            PopOSAllOverThePlace,
474            #[sea_orm(string_value = "Pop!_OS22.04LTS")]
475            PopOSWithVersion,
476            #[sea_orm(string_value = "22.04LTSPop!_OS")]
477            PopOSWithVersionPrefix,
478            #[sea_orm(string_value = "!_")]
479            PopOSJustTheSymbols,
480            #[sea_orm(string_value = "")]
481            Nothing,
482            // This WILL fail:
483            // Both PopOs and PopOS will create identifier "Popos"
484            // #[sea_orm(string_value = "PopOs")]
485            // PopOSLowerCase,
486        }
487        let values = [
488            "Pop!_OS",
489            "Pop\u{2757}_OS",
490            "Pop!_操作系统",
491            "PopOS",
492            "Pop OS",
493            "Pop!OS",
494            "Pop_OS",
495            "!PopOS_",
496            "Pop!_OS22.04LTS",
497            "22.04LTSPop!_OS",
498            "!_",
499            "",
500        ];
501        for (variant, val) in PopOSTypos::iter().zip(values) {
502            assert_eq!(variant.to_value(), val);
503            assert_eq!(PopOSTypos::try_from_value(&val.to_owned()), Ok(variant));
504        }
505
506        #[derive(Clone, Debug, PartialEq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
507        #[sea_orm(
508            rs_type = "String",
509            db_type = "String(StringLen::None)",
510            enum_name = "conflicting_string_values"
511        )]
512        pub enum ConflictingStringValues {
513            #[sea_orm(string_value = "")]
514            Member1,
515            #[sea_orm(string_value = "$")]
516            Member2,
517            #[sea_orm(string_value = "$$")]
518            Member3,
519            #[sea_orm(string_value = "AB")]
520            Member4,
521            #[sea_orm(string_value = "A_B")]
522            Member5,
523            #[sea_orm(string_value = "A$B")]
524            Member6,
525            #[sea_orm(string_value = "0 123")]
526            Member7,
527        }
528        type EnumVariant = ConflictingStringValuesVariant;
529        assert_eq!(EnumVariant::__Empty.to_string(), "");
530        assert_eq!(EnumVariant::_0x24.to_string(), "$");
531        assert_eq!(EnumVariant::_0x240x24.to_string(), "$$");
532        assert_eq!(EnumVariant::Ab.to_string(), "AB");
533        assert_eq!(EnumVariant::A0x5Fb.to_string(), "A_B");
534        assert_eq!(EnumVariant::A0x24B.to_string(), "A$B");
535        assert_eq!(EnumVariant::_0x300x20123.to_string(), "0 123");
536    }
537
538    #[test]
539    fn test_derive_display() {
540        use crate::DeriveDisplay;
541
542        #[derive(DeriveDisplay)]
543        enum DisplayTea {
544            EverydayTea,
545            #[sea_orm(display_value = "Breakfast Tea")]
546            BreakfastTea,
547        }
548        assert_eq!(format!("{}", DisplayTea::EverydayTea), "EverydayTea");
549        assert_eq!(format!("{}", DisplayTea::BreakfastTea), "Breakfast Tea");
550    }
551}