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

implement DeriveIden in sea-orm only #1740

Merged
merged 12 commits into from
Jul 13, 2023
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
rust_decimal = { version = "1", default-features = false, optional = true }
bigdecimal = { version = "0.3", default-features = false, optional = true }
sea-orm-macros = { version = "0.12.0-rc.4", path = "sea-orm-macros", default-features = false, features = ["strum"] }
sea-query = { version = "0.29.0-rc.2", features = ["thread-safe", "hashable-value"] }
sea-query = { version = "0.29.0", features = ["thread-safe", "hashable-value"] }
sea-query-binder = { version = "0.4.0-rc.2", default-features = false, optional = true }
strum = { version = "0.24", default-features = false }
serde = { version = "1.0", default-features = false }
Expand Down
13 changes: 13 additions & 0 deletions issues/1473/Cargo.toml
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"]
17 changes: 17 additions & 0 deletions issues/1473/src/main.rs
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");
}
146 changes: 146 additions & 0 deletions sea-orm-macros/src/derives/derive_iden.rs
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");
}),
}
}
2 changes: 2 additions & 0 deletions sea-orm-macros/src/derives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod active_model;
mod active_model_behavior;
mod attributes;
mod column;
mod derive_iden;
mod entity;
mod entity_model;
mod from_query_result;
Expand All @@ -24,6 +25,7 @@ pub use active_enum_display::*;
pub use active_model::*;
pub use active_model_behavior::*;
pub use column::*;
pub use derive_iden::*;
pub use entity::*;
pub use entity_model::*;
pub use from_query_result::*;
Expand Down
42 changes: 42 additions & 0 deletions sea-orm-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,3 +852,45 @@ pub fn derive_active_enum_display(input: TokenStream) -> TokenStream {
Err(e) => e.to_compile_error().into(),
}
}

/// The DeriveIden derive macro will implement `sea_orm::sea_query::Iden` for simplify Iden implementation.
///
/// ## Usage
///
/// ```rust
/// use sea_orm::DeriveIden;
///
/// #[derive(DeriveIden)]
/// pub enum Class {
/// Id,
/// Title,
/// Text,
/// }
///
/// #[derive(DeriveIden)]
/// struct Glyph;
/// ```
///
/// You can use iden = "" to customize the name
/// ```
/// use sea_orm::DeriveIden;
///
/// #[derive(DeriveIden)]
/// pub enum Class {
/// Id,
/// #[sea_orm(iden = "turtle")]
/// Title,
/// #[sea_orm(iden = "TeXt")]
/// Text,
/// }
/// ```
#[cfg(feature = "derive")]
#[proc_macro_derive(DeriveIden, attributes(sea_orm))]
pub fn derive_iden(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input as DeriveInput);

match derives::expand_derive_iden(derive_input) {
Ok(token_stream) => token_stream.into(),
Err(e) => e.to_compile_error().into(),
}
}
2 changes: 1 addition & 1 deletion src/entity/column.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{EntityName, IdenStatic, IntoSimpleExpr, Iterable};
use crate::{self as sea_orm, EntityName, IdenStatic, IntoSimpleExpr, Iterable};
use sea_query::{
Alias, BinOper, DynIden, Expr, Iden, IntoIden, SeaRc, SelectStatement, SimpleExpr, Value,
};
Expand Down
8 changes: 3 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,15 +349,13 @@ pub use schema::*;
#[cfg(feature = "macros")]
pub use sea_orm_macros::{
DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn,
DeriveCustomColumn, DeriveDisplay, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel,
DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey, DeriveRelatedEntity,
DeriveRelation, FromJsonQueryResult, FromQueryResult,
DeriveCustomColumn, DeriveDisplay, DeriveEntity, DeriveEntityModel, DeriveIden,
DeriveIntoActiveModel, DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey,
DeriveRelatedEntity, DeriveRelation, FromJsonQueryResult, FromQueryResult,
};

pub use sea_query;
pub use sea_query::Iden;
#[cfg(feature = "macros")]
pub use sea_query::Iden as DeriveIden;

pub use sea_orm_macros::EnumIter;
pub use strum;
46 changes: 46 additions & 0 deletions tests/derive_iden_tests.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is lovely.

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(())
}