More ergonomic DeriveActiveEnum derive macro

This commit is contained in:
Billy Chan 2021-11-03 15:38:42 +08:00
parent 2f7c9ccda7
commit bb78a1d709
No known key found for this signature in database
GPG Key ID: A2D690CAC7DF3CC7
4 changed files with 71 additions and 20 deletions

View File

@ -1,3 +1,4 @@
use heck::CamelCase;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{punctuated::Punctuated, token::Comma, Lit, LitInt, LitStr, Meta};
@ -10,6 +11,7 @@ enum Error {
struct ActiveEnum {
ident: syn::Ident,
enum_name: String,
rs_type: TokenStream,
db_type: TokenStream,
is_string: bool,
@ -27,6 +29,7 @@ impl ActiveEnum {
let ident_span = input.ident.span();
let ident = input.ident;
let mut enum_name = ident.to_string().to_camel_case();
let mut rs_type = Err(Error::TT(quote_spanned! {
ident_span => compile_error!("Missing macro attribute `rs_type`");
}));
@ -52,8 +55,22 @@ impl ActiveEnum {
}
} else if name == "db_type" {
if let Lit::Str(litstr) = &nv.lit {
db_type = syn::parse_str::<TokenStream>(&litstr.value())
.map_err(Error::Syn);
let s = litstr.value();
match s.as_ref() {
"Enum" => {
db_type = Ok(quote! {
Enum(Self::name(), Self::values())
})
}
_ => {
db_type = syn::parse_str::<TokenStream>(&s)
.map_err(Error::Syn);
}
}
}
} else if name == "enum_name" {
if let Lit::Str(litstr) = &nv.lit {
enum_name = litstr.value();
}
}
}
@ -125,6 +142,7 @@ impl ActiveEnum {
Ok(ActiveEnum {
ident,
enum_name,
rs_type: rs_type?,
db_type: db_type?,
is_string,
@ -141,6 +159,7 @@ impl ActiveEnum {
fn impl_active_enum(&self) -> TokenStream {
let Self {
ident,
enum_name,
rs_type,
db_type,
is_string,
@ -181,6 +200,10 @@ impl ActiveEnum {
impl sea_orm::ActiveEnum for #ident {
type Value = #rs_type;
fn name() -> String {
#enum_name.to_owned()
}
fn to_value(&self) -> Self::Value {
match self {
#( Self::#variant_idents => #variant_values, )*

View File

@ -509,6 +509,9 @@ pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream {
/// - `db_type`: Define `ColumnType` returned by `ActiveEnum::db_type()`
/// - Possible values: all available enum variants of `ColumnType`, e.g. `String(None)`, `String(Some(1))`, `Integer`
/// - Note that value has to be passed as string, i.e. `db_type = "Integer"`
/// - `enum_name`: Define `String` returned by `ActiveEnum::name()`
/// - This attribute is optional with default value being the name of enum in camel-case
/// - Note that value has to be passed as string, i.e. `db_type = "Integer"`
///
/// - For enum variant
/// - `string_value` or `num_value`:

View File

@ -1,4 +1,4 @@
use crate::{ColumnDef, DbErr, TryGetable};
use crate::{ColumnDef, DbErr, Iterable, TryGetable};
use sea_query::{Nullable, Value, ValueType};
/// A Rust representation of enum defined in database.
@ -17,8 +17,12 @@ use sea_query::{Nullable, Value, ValueType};
/// use sea_orm::entity::prelude::*;
///
/// // Using the derive macro
/// #[derive(Debug, PartialEq, DeriveActiveEnum)]
/// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")]
/// #[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)]
/// #[sea_orm(
/// rs_type = "String",
/// db_type = "String(Some(1))",
/// enum_name = "category"
/// )]
/// pub enum DeriveCategory {
/// #[sea_orm(string_value = "B")]
/// Big,
@ -27,7 +31,7 @@ use sea_query::{Nullable, Value, ValueType};
/// }
///
/// // Implementing it manually
/// #[derive(Debug, PartialEq)]
/// #[derive(Debug, PartialEq, EnumIter)]
/// pub enum Category {
/// Big,
/// Small,
@ -38,6 +42,11 @@ use sea_query::{Nullable, Value, ValueType};
/// type Value = String;
///
/// // Will be atomically generated by `DeriveActiveEnum`
/// fn name() -> String {
/// "category".to_owned()
/// }
///
/// // Will be atomically generated by `DeriveActiveEnum`
/// fn to_value(&self) -> Self::Value {
/// match self {
/// Self::Big => "B",
@ -71,7 +80,7 @@ use sea_query::{Nullable, Value, ValueType};
/// use sea_orm::entity::prelude::*;
///
/// // Define the `Category` active enum
/// #[derive(Debug, Clone, PartialEq, DeriveActiveEnum)]
/// #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
/// #[sea_orm(rs_type = "String", db_type = "String(Some(1))")]
/// pub enum Category {
/// #[sea_orm(string_value = "B")]
@ -95,10 +104,13 @@ use sea_query::{Nullable, Value, ValueType};
///
/// impl ActiveModelBehavior for ActiveModel {}
/// ```
pub trait ActiveEnum: Sized {
pub trait ActiveEnum: Sized + Iterable {
/// Define the Rust type that each enum variant represents.
type Value: Into<Value> + ValueType + Nullable + TryGetable;
/// Get the name of enum
fn name() -> String;
/// Convert enum variant into the corresponding value.
fn to_value(&self) -> Self::Value;
@ -107,6 +119,11 @@ pub trait ActiveEnum: Sized {
/// Get the database column definition of this active enum.
fn db_type() -> ColumnDef;
/// Get the name of all enum variants
fn values() -> Vec<Self::Value> {
Self::iter().map(|s| s.to_value()).collect()
}
}
#[cfg(test)]
@ -117,7 +134,7 @@ mod tests {
#[test]
fn active_enum_string() {
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, EnumIter)]
pub enum Category {
Big,
Small,
@ -126,6 +143,10 @@ mod tests {
impl ActiveEnum for Category {
type Value = String;
fn name() -> String {
"category".to_owned()
}
fn to_value(&self) -> Self::Value {
match self {
Self::Big => "B",
@ -150,8 +171,12 @@ mod tests {
}
}
#[derive(Debug, PartialEq, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "String(Some(1))")]
#[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(
rs_type = "String",
db_type = "String(Some(1))",
enum_name = "category"
)]
pub enum DeriveCategory {
#[sea_orm(string_value = "B")]
Big,
@ -195,13 +220,16 @@ mod tests {
assert_eq!(Category::db_type(), ColumnType::String(Some(1)).def());
assert_eq!(DeriveCategory::db_type(), ColumnType::String(Some(1)).def());
assert_eq!(Category::name(), DeriveCategory::name());
assert_eq!(Category::values(), DeriveCategory::values());
}
#[test]
fn active_enum_derive_signed_integers() {
macro_rules! test_int {
($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
#[derive(Debug, PartialEq, DeriveActiveEnum)]
#[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = $rs_type, db_type = $db_type)]
pub enum $ident {
#[sea_orm(num_value = 1)]
@ -241,7 +269,7 @@ mod tests {
fn active_enum_derive_unsigned_integers() {
macro_rules! test_uint {
($ident: ident, $rs_type: expr, $db_type: expr, $col_def: ident) => {
#[derive(Debug, PartialEq, DeriveActiveEnum)]
#[derive(Debug, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = $rs_type, db_type = $db_type)]
pub enum $ident {
#[sea_orm(num_value = 1)]

View File

@ -15,7 +15,7 @@ pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)]
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "String(Some(1))")]
pub enum Category {
#[sea_orm(string_value = "B")]
@ -24,7 +24,7 @@ pub enum Category {
Small,
}
#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)]
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "i32", db_type = r#"Integer"#)]
pub enum Color {
#[sea_orm(num_value = 0)]
@ -33,11 +33,8 @@ pub enum Color {
White,
}
#[derive(Debug, Clone, PartialEq, DeriveActiveEnum)]
#[sea_orm(
rs_type = "String",
db_type = r#"Enum("tea".to_owned(), vec!["EverydayTea".to_owned(), "BreakfastTea".to_owned()])"#
)]
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
pub enum Tea {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,