1use crate::{ColIdx, ColumnDef, DbErr, Iterable, QueryResult, TryFromU64, TryGetError, TryGetable};
2use sea_query::{DynIden, Expr, 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 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 }
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}