-
-
Notifications
You must be signed in to change notification settings - Fork 532
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement DeriveIden in sea-orm only (#1740)
* WIP, implementing Iden * completed implementation for DeriveIden and added basic test cases * added feature flag to prevent sea-orm::sea-query::DeriveIden from crashing when sea-query is not used * fixed doc test and adjusted test case * enable `sea-query-derive`'s `sea-orm` feature * Bump `sea-query-derive` to v0.4 * Update Cargo.toml * Update Cargo.toml * adjusted test cases and updated so that iden attribute will not be snake cased * Update Cargo.toml * Update main.rs --------- Co-authored-by: Billy Chan <[email protected]> Co-authored-by: Chris Tsang <[email protected]>
- Loading branch information
1 parent
b4c1a69
commit 866025a
Showing
9 changed files
with
271 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[workspace] | ||
# A separate workspace | ||
|
||
[package] | ||
name = "sea-orm-issues-1473" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies.sea-orm] | ||
path = "../../" | ||
default-features = false | ||
features = ["macros", "runtime-tokio-native-tls"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use sea_orm::{sea_query::{self, Iden}}; | ||
|
||
#[derive(Iden)] | ||
enum Character { | ||
Table, | ||
Id, | ||
} | ||
|
||
#[derive(Iden)] | ||
struct Glyph; | ||
|
||
fn main() { | ||
assert_eq!(Character::Table.to_string(), "character"); | ||
assert_eq!(Character::Id.to_string(), "id"); | ||
|
||
assert_eq!(Glyph.to_string(), "glyph"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
use heck::ToSnakeCase; | ||
use proc_macro2::{self, TokenStream}; | ||
use quote::{quote, quote_spanned}; | ||
use syn::{ | ||
punctuated::Punctuated, DataEnum, DataStruct, DeriveInput, Expr, Fields, LitStr, Variant, | ||
}; | ||
|
||
fn must_be_valid_iden(name: &str) -> bool { | ||
// can only begin with [a-z_] | ||
name.chars() | ||
.take(1) | ||
.all(|c| c == '_' || c.is_ascii_alphabetic()) | ||
&& name.chars().all(|c| c == '_' || c.is_ascii_alphanumeric()) | ||
} | ||
|
||
fn impl_iden_for_unit_struct( | ||
ident: &proc_macro2::Ident, | ||
new_iden: &str, | ||
) -> proc_macro2::TokenStream { | ||
let prepare = if must_be_valid_iden(new_iden) { | ||
quote! { | ||
fn prepare(&self, s: &mut dyn ::std::fmt::Write, q: sea_orm::sea_query::Quote) { | ||
write!(s, "{}", q.left()).unwrap(); | ||
self.unquoted(s); | ||
write!(s, "{}", q.right()).unwrap(); | ||
} | ||
} | ||
} else { | ||
quote! {} | ||
}; | ||
quote! { | ||
impl sea_orm::sea_query::Iden for #ident { | ||
#prepare | ||
|
||
fn unquoted(&self, s: &mut dyn ::std::fmt::Write) { | ||
write!(s, #new_iden).unwrap(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn impl_iden_for_enum( | ||
ident: &proc_macro2::Ident, | ||
variants: Punctuated<Variant, syn::token::Comma>, | ||
) -> proc_macro2::TokenStream { | ||
let variants = variants.iter(); | ||
let mut all_valid = true; | ||
|
||
let match_pair: Vec<TokenStream> = variants | ||
.map(|v| { | ||
let var_ident = &v.ident; | ||
let mut var_name = var_ident.to_string().to_snake_case(); | ||
v.attrs | ||
.iter() | ||
.filter(|attr| attr.path().is_ident("sea_orm")) | ||
.try_for_each(|attr| { | ||
attr.parse_nested_meta(|meta| { | ||
if meta.path.is_ident("iden") { | ||
let litstr: LitStr = meta.value()?.parse()?; | ||
var_name = litstr.value(); | ||
all_valid &= must_be_valid_iden(var_name.as_str()); | ||
} else { | ||
// Reads the value expression to advance the parse stream. | ||
// Some parameters do not have any value, | ||
// so ignoring an error occurred here. | ||
let _: Option<Expr> = meta.value().and_then(|v| v.parse()).ok(); | ||
} | ||
Ok(()) | ||
}) | ||
}) | ||
.expect("something something"); | ||
quote! { Self::#var_ident => write!(s, "{}", #var_name).unwrap() } | ||
}) | ||
.collect(); | ||
|
||
let match_arms: TokenStream = quote! { #(#match_pair),* }; | ||
|
||
let prepare = if all_valid { | ||
quote! { | ||
fn prepare(&self, s: &mut dyn ::std::fmt::Write, q: sea_orm::sea_query::Quote) { | ||
write!(s, "{}", q.left()).unwrap(); | ||
self.unquoted(s); | ||
write!(s, "{}", q.right()).unwrap(); | ||
} | ||
} | ||
} else { | ||
quote! {} | ||
}; | ||
|
||
quote! { | ||
impl sea_orm::sea_query::Iden for #ident { | ||
#prepare | ||
|
||
fn unquoted(&self, s: &mut dyn ::std::fmt::Write) { | ||
match self { | ||
#match_arms | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
|
||
pub fn expand_derive_iden(input: DeriveInput) -> syn::Result<TokenStream> { | ||
let DeriveInput { ident, data, .. } = input; | ||
|
||
let mut new_iden: TokenStream = ident.to_string().to_snake_case().parse().unwrap(); | ||
input | ||
.attrs | ||
.iter() | ||
.filter(|attr| attr.path().is_ident("sea_orm")) | ||
.try_for_each(|attr| { | ||
attr.parse_nested_meta(|meta| { | ||
if meta.path.is_ident("iden") { | ||
let litstr: LitStr = meta.value()?.parse()?; | ||
new_iden = syn::parse_str::<TokenStream>(&litstr.value())?; | ||
} else { | ||
// Reads the value expression to advance the parse stream. | ||
// Some parameters do not have any value, | ||
// so ignoring an error occurred here. | ||
let _: Option<Expr> = meta.value().and_then(|v| v.parse()).ok(); | ||
} | ||
Ok(()) | ||
}) | ||
})?; | ||
|
||
// Currently we only support enums and unit structs | ||
match data { | ||
syn::Data::Enum(DataEnum { variants, .. }) => { | ||
if variants.is_empty() { | ||
Ok(TokenStream::new()) | ||
} else { | ||
Ok(impl_iden_for_enum(&ident, variants)) | ||
} | ||
} | ||
syn::Data::Struct(DataStruct { | ||
fields: Fields::Unit, | ||
.. | ||
}) => Ok(impl_iden_for_unit_struct( | ||
&ident, | ||
new_iden.to_string().as_str(), | ||
)), | ||
_ => Ok(quote_spanned! { | ||
ident.span() => compile_error!("you can only derive DeriveIden on unit struct or enum"); | ||
}), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
pub mod common; | ||
pub use common::{features::*, setup::*, TestContext}; | ||
use sea_orm::entity::prelude::*; | ||
use sea_orm_macros::DeriveIden; | ||
|
||
#[derive(DeriveIden)] | ||
pub enum Class { | ||
Id, | ||
Title, | ||
Text, | ||
} | ||
|
||
#[derive(DeriveIden)] | ||
pub enum Book { | ||
Id, | ||
#[sea_orm(iden = "turtle")] | ||
Title, | ||
#[sea_orm(iden = "TeXt")] | ||
Text, | ||
#[sea_orm(iden = "ty_pe")] | ||
Type, | ||
} | ||
|
||
#[derive(DeriveIden)] | ||
struct Glyph; | ||
|
||
#[derive(DeriveIden)] | ||
#[sea_orm(iden = "weRd")] | ||
struct Word; | ||
|
||
#[test] | ||
fn main() -> Result<(), DbErr> { | ||
assert_eq!(Class::Id.to_string(), "id"); | ||
assert_eq!(Class::Title.to_string(), "title"); | ||
assert_eq!(Class::Text.to_string(), "text"); | ||
|
||
assert_eq!(Book::Id.to_string(), "id"); | ||
assert_eq!(Book::Title.to_string(), "turtle"); | ||
assert_eq!(Book::Text.to_string(), "TeXt"); | ||
assert_eq!(Book::Type.to_string(), "ty_pe"); | ||
|
||
assert_eq!(Glyph.to_string(), "glyph"); | ||
|
||
assert_eq!(Word.to_string(), "weRd"); | ||
Ok(()) | ||
} |