Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enumeration #258

Merged
merged 45 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8627c8d
Draft ActiveEnum
billy1624 Oct 19, 2021
18f3715
Fixup
billy1624 Oct 20, 2021
30e17a2
Better error messages
billy1624 Oct 20, 2021
e177a33
Minimal trait bounds
billy1624 Oct 20, 2021
5b07200
More tests
billy1624 Oct 20, 2021
b1e10ae
Refactoring
billy1624 Oct 20, 2021
bf16635
Fix clippy warnings
billy1624 Oct 20, 2021
ceba3ef
Add docs
billy1624 Oct 20, 2021
eed8b7c
Add docs
billy1624 Oct 20, 2021
388bf2c
Fixup
billy1624 Oct 20, 2021
f1ef7d9
Add `DbErr::Type`
billy1624 Oct 20, 2021
868a469
Test integer enum
billy1624 Oct 20, 2021
d525c71
Merge remote-tracking branch 'origin/master' into active-enum
billy1624 Oct 20, 2021
7346084
WIP
billy1624 Oct 20, 2021
80c7200
Try Postgres enum
billy1624 Oct 21, 2021
20c66b2
Refactoring
billy1624 Oct 21, 2021
1ee2dab
Fixup
billy1624 Oct 21, 2021
8858d64
create_table_from_entity with DB backend
billy1624 Oct 25, 2021
f20c649
Tests all DB
billy1624 Oct 25, 2021
6059cdd
Remove unused EnumIter
billy1624 Oct 25, 2021
4b1cac7
Refactoring
billy1624 Oct 25, 2021
cf52839
Typo
billy1624 Oct 25, 2021
2ee376d
Try `EnumValue`
billy1624 Oct 26, 2021
db22e70
Refactoring
billy1624 Oct 26, 2021
ded28be
Refactoring
billy1624 Oct 26, 2021
fac528a
Refactoring
billy1624 Oct 27, 2021
a1f57ec
Merge pull request #274 from SeaQL/active-enum-3
billy1624 Oct 27, 2021
e04495b
Refactoring
billy1624 Oct 27, 2021
55de196
Add `create_enum_from_entity`
billy1624 Oct 27, 2021
f88c725
Fixup
billy1624 Oct 27, 2021
70e76eb
Merge pull request #261 from SeaQL/active-enum-1
billy1624 Oct 27, 2021
fe64d53
Merge remote-tracking branch 'origin/master' into active-enum
billy1624 Oct 27, 2021
e21af53
Fix clippy warnings
billy1624 Oct 27, 2021
6018d3f
Merge remote-tracking branch 'origin/master' into active-enum
billy1624 Nov 2, 2021
a95ee31
Use sea-query master
billy1624 Nov 2, 2021
6554230
Add docs
billy1624 Nov 2, 2021
858e1e0
Update docs
billy1624 Nov 2, 2021
0bbb50b
Merge remote-tracking branch 'origin/master' into active-enum
billy1624 Nov 3, 2021
2f7c9cc
Refactoring
billy1624 Nov 3, 2021
bb78a1d
More ergonomic `DeriveActiveEnum` derive macro
billy1624 Nov 3, 2021
2b841b1
Refactoring
billy1624 Nov 4, 2021
f64f1e9
`DeriveActiveEnum` generate code that depends on sea-query inside sea…
billy1624 Nov 4, 2021
67bb168
Correctly apply filters on enum columns
billy1624 Nov 4, 2021
10f3de0
Only `eq` & `ne` operators with enum casting
billy1624 Nov 5, 2021
47e2486
Refactoring
billy1624 Nov 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ futures-util = { version = "^0.3" }
log = { version = "^0.4", optional = true }
rust_decimal = { version = "^1", optional = true }
sea-orm-macros = { version = "^0.3.1", path = "sea-orm-macros", optional = true }
sea-query = { version = "^0.18.0", features = ["thread-safe"] }
sea-query = { version = "^0.18.0", git = "https://github.com/SeaQL/sea-query.git", features = ["thread-safe"] }
sea-strum = { version = "^0.21", features = ["derive", "sea-orm"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1", optional = true }
Expand Down
263 changes: 263 additions & 0 deletions sea-orm-macros/src/derives/active_enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{punctuated::Punctuated, token::Comma, Lit, LitInt, LitStr, Meta};

enum Error {
InputNotEnum,
Syn(syn::Error),
TT(TokenStream),
}

struct ActiveEnum {
ident: syn::Ident,
rs_type: TokenStream,
db_type: TokenStream,
is_string: bool,
variants: Vec<ActiveEnumVariant>,
}

struct ActiveEnumVariant {
ident: syn::Ident,
string_value: Option<LitStr>,
num_value: Option<LitInt>,
}

impl ActiveEnum {
fn new(input: syn::DeriveInput) -> Result<Self, Error> {
let ident_span = input.ident.span();
let ident = input.ident;

let mut rs_type = Err(Error::TT(quote_spanned! {
ident_span => compile_error!("Missing macro attribute `rs_type`");
}));
let mut db_type = Err(Error::TT(quote_spanned! {
ident_span => compile_error!("Missing macro attribute `db_type`");
}));
for attr in input.attrs.iter() {
if let Some(ident) = attr.path.get_ident() {
if ident != "sea_orm" {
continue;
}
} else {
continue;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
for meta in list.iter() {
if let Meta::NameValue(nv) = meta {
if let Some(name) = nv.path.get_ident() {
if name == "rs_type" {
if let Lit::Str(litstr) = &nv.lit {
rs_type = syn::parse_str::<TokenStream>(&litstr.value())
.map_err(Error::Syn);
}
} 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 variant_vec = match input.data {
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
_ => return Err(Error::InputNotEnum),
};

let mut is_string = false;
let mut is_int = false;
let mut variants = Vec::new();
for variant in variant_vec {
let variant_span = variant.ident.span();
let mut string_value = None;
let mut num_value = None;
for attr in variant.attrs.iter() {
if let Some(ident) = attr.path.get_ident() {
if ident != "sea_orm" {
continue;
}
} else {
continue;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
{
for meta in list {
if let Meta::NameValue(nv) = meta {
if let Some(name) = nv.path.get_ident() {
if name == "string_value" {
if let Lit::Str(lit) = nv.lit {
is_string = true;
string_value = Some(lit);
}
} else if name == "num_value" {
if let Lit::Int(lit) = nv.lit {
is_int = true;
num_value = Some(lit);
}
}
}
}
}
}
}

if is_string && is_int {
return Err(Error::TT(quote_spanned! {
ident_span => compile_error!("All enum variants should specify the same `*_value` macro attribute, either `string_value` or `num_value` but not both");
}));
}

if string_value.is_none() && num_value.is_none() {
return Err(Error::TT(quote_spanned! {
variant_span => compile_error!("Missing macro attribute, either `string_value` or `num_value` should be specified");
}));
}

variants.push(ActiveEnumVariant {
ident: variant.ident,
string_value,
num_value,
});
}

Ok(ActiveEnum {
ident,
rs_type: rs_type?,
db_type: db_type?,
is_string,
variants,
})
}

fn expand(&self) -> syn::Result<TokenStream> {
let expanded_impl_active_enum = self.impl_active_enum();

Ok(expanded_impl_active_enum)
}

fn impl_active_enum(&self) -> TokenStream {
let Self {
ident,
rs_type,
db_type,
is_string,
variants,
} = self;

let variant_idents: Vec<syn::Ident> = variants
.iter()
.map(|variant| variant.ident.clone())
.collect();

let variant_values: Vec<TokenStream> = variants
.iter()
.map(|variant| {
let variant_span = variant.ident.span();

if let Some(string_value) = &variant.string_value {
let string = string_value.value();
quote! { #string }
} else if let Some(num_value) = &variant.num_value {
quote! { #num_value }
} else {
quote_spanned! {
variant_span => compile_error!("Missing macro attribute, either `string_value` or `num_value` should be specified");
}
}
})
.collect();

let val = if *is_string {
quote! { v.as_ref() }
} else {
quote! { v }
};

quote!(
#[automatically_derived]
impl sea_orm::ActiveEnum for #ident {
type Value = #rs_type;

fn to_value(&self) -> Self::Value {
match self {
#( Self::#variant_idents => #variant_values, )*
}
.to_owned()
}

fn try_from_value(v: &Self::Value) -> Result<Self, sea_orm::DbErr> {
match #val {
#( #variant_values => Ok(Self::#variant_idents), )*
_ => Err(sea_orm::DbErr::Type(format!(
"unexpected value for {} enum: {}",
stringify!(#ident),
v
))),
}
}

fn db_type() -> sea_orm::ColumnDef {
sea_orm::ColumnType::#db_type.def()
}
}

#[automatically_derived]
#[allow(clippy::from_over_into)]
impl Into<sea_query::Value> for #ident {
fn into(self) -> sea_query::Value {
<Self as sea_orm::ActiveEnum>::to_value(&self).into()
}
}

#[automatically_derived]
impl sea_orm::TryGetable for #ident {
fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> Result<Self, sea_orm::TryGetError> {
let value = <<Self as sea_orm::ActiveEnum>::Value as sea_orm::TryGetable>::try_get(res, pre, col)?;
<Self as sea_orm::ActiveEnum>::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr)
}
}

#[automatically_derived]
impl sea_query::ValueType for #ident {
fn try_from(v: sea_query::Value) -> Result<Self, sea_query::ValueTypeErr> {
let value = <<Self as sea_orm::ActiveEnum>::Value as sea_query::ValueType>::try_from(v)?;
<Self as sea_orm::ActiveEnum>::try_from_value(&value).map_err(|_| sea_query::ValueTypeErr)
}

fn type_name() -> String {
<<Self as sea_orm::ActiveEnum>::Value as sea_query::ValueType>::type_name()
}

fn column_type() -> sea_query::ColumnType {
<Self as sea_orm::ActiveEnum>::db_type()
.get_column_type()
.to_owned()
.into()
}
}

#[automatically_derived]
impl sea_query::Nullable for #ident {
fn null() -> sea_query::Value {
<<Self as sea_orm::ActiveEnum>::Value as sea_query::Nullable>::null()
}
}
)
}
}

pub fn expand_derive_active_enum(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let ident_span = input.ident.span();

match ActiveEnum::new(input) {
Ok(model) => model.expand(),
Err(Error::InputNotEnum) => Ok(quote_spanned! {
ident_span => compile_error!("you can only derive ActiveEnum on enums");
}),
Err(Error::TT(token_stream)) => Ok(token_stream),
Err(Error::Syn(e)) => Err(e),
}
}
26 changes: 17 additions & 9 deletions sea-orm-macros/src/derives/entity_model.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::util::{escape_rust_keyword, trim_starting_raw_identifier};
use heck::CamelCase;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use quote::{format_ident, quote, quote_spanned};
use syn::{
parse::Error, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, Fields,
Lit, Meta,
Expand Down Expand Up @@ -193,8 +193,8 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
primary_keys.push(quote! { #field_name });
}

let field_type = match sql_type {
Some(t) => t,
let col_type = match sql_type {
Some(t) => quote! { sea_orm::prelude::ColumnType::#t.def() },
None => {
let field_type = &field.ty;
let temp = quote! { #field_type }
Expand All @@ -206,7 +206,7 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
} else {
temp.as_str()
};
match temp {
let col_type = match temp {
"char" => quote! { Char(None) },
"String" | "&str" => quote! { String(None) },
"u8" | "i8" => quote! { TinyInteger },
Expand All @@ -229,16 +229,24 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
"Decimal" => quote! { Decimal(None) },
"Vec<u8>" => quote! { Binary },
_ => {
return Err(Error::new(
field.span(),
format!("unrecognized type {}", temp),
))
// Assumed it's ActiveEnum if none of the above type matches
quote! {}
}
};
if col_type.is_empty() {
let field_span = field.span();
let ty = format_ident!("{}", temp);
let def = quote_spanned! { field_span => {
<#ty as ActiveEnum>::db_type()
}};
quote! { #def }
} else {
quote! { sea_orm::prelude::ColumnType::#col_type.def() }
}
}
};

let mut match_row = quote! { Self::#field_name => sea_orm::prelude::ColumnType::#field_type.def() };
let mut match_row = quote! { Self::#field_name => #col_type };
if nullable {
match_row = quote! { #match_row.nullable() };
}
Expand Down
2 changes: 2 additions & 0 deletions sea-orm-macros/src/derives/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod active_enum;
mod active_model;
mod active_model_behavior;
mod column;
Expand All @@ -9,6 +10,7 @@ mod model;
mod primary_key;
mod relation;

pub use active_enum::*;
pub use active_model::*;
pub use active_model_behavior::*;
pub use column::*;
Expand Down
32 changes: 32 additions & 0 deletions sea-orm-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,38 @@ pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream {
}
}

/// A derive macro to implement `sea_orm::ActiveEnum` trait for enums.
///
/// # Limitations
///
/// This derive macros can only be used on enums.
///
/// # Macro Attributes
///
/// All macro attributes listed below have to be annotated in the form of `#[sea_orm(attr = value)]`.
///
/// - For enum
/// - `rs_type`: Define `ActiveEnum::Value`
/// - Possible values: `String`, `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`
/// - Note that value has to be passed as string, i.e. `rs_type = "i8"`
/// - `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"`
///
/// - For enum variant
/// - `string_value` or `num_value`:
/// - For `string_value`, value should be passed as string, i.e. `string_value = "A"`
/// - For `num_value`, value should be passed as integer, i.e. `num_value = 1` or `num_value = 1i32`
/// - Note that only one of it can be specified, and all variants of an enum have to annotate with the same `*_value` macro attribute
#[proc_macro_derive(DeriveActiveEnum, attributes(sea_orm))]
pub fn derive_active_enum(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match derives::expand_derive_active_enum(input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}

/// Convert a query result into the corresponding Model.
///
/// ### Usage
Expand Down
Loading