1use crate::{ColIdx, ColumnDef, DbErr, Iterable, QueryResult, TryFromU64, TryGetError, TryGetable};
2use sea_query::{DynIden, Expr, ExprTrait, Nullable, SimpleExpr, Value, ValueType};
3
4pub trait ActiveEnum: Sized + Iterable {
113 type Value: ActiveEnumValue;
115
116 type ValueVec;
118
119 fn name() -> DynIden;
121
122 fn to_value(&self) -> Self::Value;
124
125 fn try_from_value(v: &Self::Value) -> Result<Self, DbErr>;
127
128 fn db_type() -> ColumnDef;
130
131 fn into_value(self) -> Self::Value {
133 Self::to_value(&self)
134 }
135
136 fn as_enum(&self) -> SimpleExpr {
138 Expr::val(Self::to_value(self)).as_enum(Self::name())
139 }
140
141 fn values() -> Vec<Self::Value> {
143 Self::iter().map(Self::into_value).collect()
144 }
145}
146
147pub trait ActiveEnumValue: Into<Value> + ValueType + Nullable + TryGetable {
149 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 }
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}