sea_orm/entity/
active_enum.rs

1use crate::{ColIdx, ColumnDef, DbErr, Iterable, QueryResult, TryFromU64, TryGetError, TryGetable};
2use sea_query::{DynIden, Expr, ExprTrait, 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                Err(TryGetError::DbErr(DbErr::BackendNotSupported {
161                    db: "Postgres",
162                    ctx: "ActiveEnumValue::try_get_vec_by",
163                }))
164            }
165        }
166    };
167}
168
169macro_rules! impl_active_enum_value_with_pg_array {
170    ($type:ident) => {
171        impl ActiveEnumValue for $type {
172            fn try_get_vec_by<I: ColIdx>(
173                _res: &QueryResult,
174                _index: I,
175            ) -> Result<Vec<Self>, TryGetError> {
176                #[cfg(feature = "postgres-array")]
177                {
178                    <Vec<Self>>::try_get_by(_res, _index)
179                }
180                #[cfg(not(feature = "postgres-array"))]
181                Err(TryGetError::DbErr(DbErr::BackendNotSupported {
182                    db: "Postgres",
183                    ctx: "ActiveEnumValue::try_get_vec_by (`postgres-array` not enabled)",
184                }))
185            }
186        }
187    };
188}
189
190impl_active_enum_value!(u8);
191impl_active_enum_value!(u16);
192impl_active_enum_value!(u32);
193impl_active_enum_value!(u64);
194impl_active_enum_value_with_pg_array!(String);
195impl_active_enum_value_with_pg_array!(i8);
196impl_active_enum_value_with_pg_array!(i16);
197impl_active_enum_value_with_pg_array!(i32);
198impl_active_enum_value_with_pg_array!(i64);
199
200impl<T> TryFromU64 for T
201where
202    T: ActiveEnum,
203{
204    fn try_from_u64(_: u64) -> Result<Self, DbErr> {
205        Err(DbErr::ConvertFromU64(
206            "Fail to construct ActiveEnum from a u64, if your primary key consist of a ActiveEnum field, its auto increment should be set to false.",
207        ))
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use crate as sea_orm;
214    use crate::{
215        error::*,
216        sea_query::{SeaRc, StringLen},
217        *,
218    };
219    use pretty_assertions::assert_eq;
220
221    #[test]
222    fn active_enum_string() {
223        #[derive(Debug, PartialEq, Eq, EnumIter)]
224        pub enum Category {
225            Big,
226            Small,
227        }
228
229        #[derive(Debug, DeriveIden)]
230        #[sea_orm(iden = "category")]
231        pub struct CategoryEnum;
232
233        impl ActiveEnum for Category {
234            type Value = String;
235
236            type ValueVec = Vec<String>;
237
238            fn name() -> DynIden {
239                SeaRc::new(CategoryEnum)
240            }
241
242            fn to_value(&self) -> Self::Value {
243                match self {
244                    Self::Big => "B",
245                    Self::Small => "S",
246                }
247                .to_owned()
248            }
249
250            fn try_from_value(v: &Self::Value) -> Result<Self, DbErr> {
251                match v.as_ref() {
252                    "B" => Ok(Self::Big),
253                    "S" => Ok(Self::Small),
254                    _ => Err(type_err(format!("unexpected value for Category enum: {v}"))),
255                }
256            }
257
258            fn db_type() -> ColumnDef {
259                ColumnType::String(StringLen::N(1)).def()
260            }
261        }
262
263        #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
264        #[sea_orm(
265            rs_type = "String",
266            db_type = "String(StringLen::N(1))",
267            enum_name = "category"
268        )]
269        pub enum DeriveCategory {
270            #[sea_orm(string_value = "B")]
271            Big,
272            #[sea_orm(string_value = "S")]
273            Small,
274        }
275
276        assert_eq!(Category::Big.to_value(), "B".to_owned());
277        assert_eq!(Category::Small.to_value(), "S".to_owned());
278        assert_eq!(DeriveCategory::Big.to_value(), "B".to_owned());
279        assert_eq!(DeriveCategory::Small.to_value(), "S".to_owned());
280
281        assert_eq!(
282            Category::try_from_value(&"A".to_owned()).err(),
283            Some(type_err("unexpected value for Category enum: A"))
284        );
285        assert_eq!(
286            Category::try_from_value(&"B".to_owned()).ok(),
287            Some(Category::Big)
288        );
289        assert_eq!(
290            Category::try_from_value(&"S".to_owned()).ok(),
291            Some(Category::Small)
292        );
293        assert_eq!(
294            DeriveCategory::try_from_value(&"A".to_owned()).err(),
295            Some(type_err("unexpected value for DeriveCategory enum: A"))
296        );
297        assert_eq!(
298            DeriveCategory::try_from_value(&"B".to_owned()).ok(),
299            Some(DeriveCategory::Big)
300        );
301        assert_eq!(
302            DeriveCategory::try_from_value(&"S".to_owned()).ok(),
303            Some(DeriveCategory::Small)
304        );
305
306        assert_eq!(
307            Category::db_type(),
308            ColumnType::String(StringLen::N(1)).def()
309        );
310        assert_eq!(
311            DeriveCategory::db_type(),
312            ColumnType::String(StringLen::N(1)).def()
313        );
314
315        assert_eq!(
316            Category::name().to_string(),
317            DeriveCategory::name().to_string()
318        );
319        assert_eq!(Category::values(), DeriveCategory::values());
320
321        assert_eq!(format!("{}", DeriveCategory::Big), "Big");
322        assert_eq!(format!("{}", DeriveCategory::Small), "Small");
323    }
324
325    #[test]
326    fn active_enum_derive_signed_integers() {
327        macro_rules! test_num_value_int {
328            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
329                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
330                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
331                pub enum $ident {
332                    #[sea_orm(num_value = -10)]
333                    Negative,
334                    #[sea_orm(num_value = 1)]
335                    Big,
336                    #[sea_orm(num_value = 0)]
337                    Small,
338                }
339
340                test_int!($ident, $rs_type, $db_type, $col_def);
341            };
342        }
343
344        macro_rules! test_fallback_int {
345            ($ident: ident, $fallback_type: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
346                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
347                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
348                #[repr(i32)]
349                pub enum $ident {
350                    Big = 1,
351                    Small = 0,
352                    Negative = -10,
353                }
354
355                test_int!($ident, $rs_type, $db_type, $col_def);
356            };
357        }
358
359        macro_rules! test_int {
360            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
361                assert_eq!($ident::Big.to_value(), 1);
362                assert_eq!($ident::Small.to_value(), 0);
363                assert_eq!($ident::Negative.to_value(), -10);
364
365                assert_eq!($ident::try_from_value(&1).ok(), Some($ident::Big));
366                assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small));
367                assert_eq!($ident::try_from_value(&-10).ok(), Some($ident::Negative));
368                assert_eq!(
369                    $ident::try_from_value(&2).err(),
370                    Some(type_err(format!(
371                        "unexpected value for {} enum: 2",
372                        stringify!($ident)
373                    )))
374                );
375
376                assert_eq!($ident::db_type(), ColumnType::$col_def.def());
377
378                assert_eq!(format!("{}", $ident::Big), "Big");
379                assert_eq!(format!("{}", $ident::Small), "Small");
380                assert_eq!(format!("{}", $ident::Negative), "Negative");
381            };
382        }
383
384        test_num_value_int!(I8, "i8", "TinyInteger", TinyInteger);
385        test_num_value_int!(I16, "i16", "SmallInteger", SmallInteger);
386        test_num_value_int!(I32, "i32", "Integer", Integer);
387        test_num_value_int!(I64, "i64", "BigInteger", BigInteger);
388
389        test_fallback_int!(I8Fallback, i8, "i8", "TinyInteger", TinyInteger);
390        test_fallback_int!(I16Fallback, i16, "i16", "SmallInteger", SmallInteger);
391        test_fallback_int!(I32Fallback, i32, "i32", "Integer", Integer);
392        test_fallback_int!(I64Fallback, i64, "i64", "BigInteger", BigInteger);
393    }
394
395    #[test]
396    fn active_enum_derive_unsigned_integers() {
397        macro_rules! test_num_value_uint {
398            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
399                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
400                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
401                pub enum $ident {
402                    #[sea_orm(num_value = 1)]
403                    Big,
404                    #[sea_orm(num_value = 0)]
405                    Small,
406                }
407
408                test_uint!($ident, $rs_type, $db_type, $col_def);
409            };
410        }
411
412        macro_rules! test_fallback_uint {
413            ($ident: ident, $fallback_type: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
414                #[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
415                #[sea_orm(rs_type = $rs_type, db_type = $db_type)]
416                #[repr($fallback_type)]
417                pub enum $ident {
418                    Big = 1,
419                    Small = 0,
420                }
421
422                test_uint!($ident, $rs_type, $db_type, $col_def);
423            };
424        }
425
426        macro_rules! test_uint {
427            ($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
428                assert_eq!($ident::Big.to_value(), 1);
429                assert_eq!($ident::Small.to_value(), 0);
430
431                assert_eq!($ident::try_from_value(&1).ok(), Some($ident::Big));
432                assert_eq!($ident::try_from_value(&0).ok(), Some($ident::Small));
433                assert_eq!(
434                    $ident::try_from_value(&2).err(),
435                    Some(type_err(format!(
436                        "unexpected value for {} enum: 2",
437                        stringify!($ident)
438                    )))
439                );
440
441                assert_eq!($ident::db_type(), ColumnType::$col_def.def());
442
443                assert_eq!(format!("{}", $ident::Big), "Big");
444                assert_eq!(format!("{}", $ident::Small), "Small");
445            };
446        }
447
448        test_num_value_uint!(U8, "u8", "TinyInteger", TinyInteger);
449        test_num_value_uint!(U16, "u16", "SmallInteger", SmallInteger);
450        test_num_value_uint!(U32, "u32", "Integer", Integer);
451        test_num_value_uint!(U64, "u64", "BigInteger", BigInteger);
452
453        test_fallback_uint!(U8Fallback, u8, "u8", "TinyInteger", TinyInteger);
454        test_fallback_uint!(U16Fallback, u16, "u16", "SmallInteger", SmallInteger);
455        test_fallback_uint!(U32Fallback, u32, "u32", "Integer", Integer);
456        test_fallback_uint!(U64Fallback, u64, "u64", "BigInteger", BigInteger);
457    }
458
459    #[test]
460    fn escaped_non_uax31() {
461        #[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy)]
462        #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "pop_os_names_typos")]
463        pub enum PopOSTypos {
464            #[sea_orm(string_value = "Pop!_OS")]
465            PopOSCorrect,
466            #[sea_orm(string_value = "Pop\u{2757}_OS")]
467            PopOSEmoji,
468            #[sea_orm(string_value = "Pop!_操作系统")]
469            PopOSChinese,
470            #[sea_orm(string_value = "PopOS")]
471            PopOSASCIIOnly,
472            #[sea_orm(string_value = "Pop OS")]
473            PopOSASCIIOnlyWithSpace,
474            #[sea_orm(string_value = "Pop!OS")]
475            PopOSNoUnderscore,
476            #[sea_orm(string_value = "Pop_OS")]
477            PopOSNoExclaimation,
478            #[sea_orm(string_value = "!PopOS_")]
479            PopOSAllOverThePlace,
480            #[sea_orm(string_value = "Pop!_OS22.04LTS")]
481            PopOSWithVersion,
482            #[sea_orm(string_value = "22.04LTSPop!_OS")]
483            PopOSWithVersionPrefix,
484            #[sea_orm(string_value = "!_")]
485            PopOSJustTheSymbols,
486            #[sea_orm(string_value = "")]
487            Nothing,
488            // This WILL fail:
489            // Both PopOs and PopOS will create identifier "Popos"
490            // #[sea_orm(string_value = "PopOs")]
491            // PopOSLowerCase,
492        }
493        let values = [
494            "Pop!_OS",
495            "Pop\u{2757}_OS",
496            "Pop!_操作系统",
497            "PopOS",
498            "Pop OS",
499            "Pop!OS",
500            "Pop_OS",
501            "!PopOS_",
502            "Pop!_OS22.04LTS",
503            "22.04LTSPop!_OS",
504            "!_",
505            "",
506        ];
507        for (variant, val) in PopOSTypos::iter().zip(values) {
508            assert_eq!(variant.to_value(), val);
509            assert_eq!(PopOSTypos::try_from_value(&val.to_owned()), Ok(variant));
510        }
511
512        #[derive(Clone, Debug, PartialEq, EnumIter, DeriveActiveEnum, DeriveDisplay)]
513        #[sea_orm(
514            rs_type = "String",
515            db_type = "String(StringLen::None)",
516            enum_name = "conflicting_string_values"
517        )]
518        pub enum ConflictingStringValues {
519            #[sea_orm(string_value = "")]
520            Member1,
521            #[sea_orm(string_value = "$")]
522            Member2,
523            #[sea_orm(string_value = "$$")]
524            Member3,
525            #[sea_orm(string_value = "AB")]
526            Member4,
527            #[sea_orm(string_value = "A_B")]
528            Member5,
529            #[sea_orm(string_value = "A$B")]
530            Member6,
531            #[sea_orm(string_value = "0 123")]
532            Member7,
533        }
534        type EnumVariant = ConflictingStringValuesVariant;
535        assert_eq!(EnumVariant::__Empty.to_string(), "");
536        assert_eq!(EnumVariant::_0x24.to_string(), "$");
537        assert_eq!(EnumVariant::_0x240x24.to_string(), "$$");
538        assert_eq!(EnumVariant::Ab.to_string(), "AB");
539        assert_eq!(EnumVariant::A0x5Fb.to_string(), "A_B");
540        assert_eq!(EnumVariant::A0x24B.to_string(), "A$B");
541        assert_eq!(EnumVariant::_0x300x20123.to_string(), "0 123");
542    }
543
544    #[test]
545    fn test_derive_display() {
546        use crate::DeriveDisplay;
547
548        #[derive(DeriveDisplay)]
549        enum DisplayTea {
550            EverydayTea,
551            #[sea_orm(display_value = "Breakfast Tea")]
552            BreakfastTea,
553        }
554        assert_eq!(format!("{}", DisplayTea::EverydayTea), "EverydayTea");
555        assert_eq!(format!("{}", DisplayTea::BreakfastTea), "Breakfast Tea");
556    }
557}