Skip to content

Commit 866025a

Browse files
darkmmonbilly1624tyt2y3
authored
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]>
1 parent b4c1a69 commit 866025a

File tree

9 files changed

+271
-7
lines changed

9 files changed

+271
-7
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
3434
rust_decimal = { version = "1", default-features = false, optional = true }
3535
bigdecimal = { version = "0.3", default-features = false, optional = true }
3636
sea-orm-macros = { version = "0.12.0-rc.4", path = "sea-orm-macros", default-features = false, features = ["strum"] }
37-
sea-query = { version = "0.29.0-rc.2", features = ["thread-safe", "hashable-value"] }
37+
sea-query = { version = "0.29.0", features = ["thread-safe", "hashable-value"] }
3838
sea-query-binder = { version = "0.4.0-rc.2", default-features = false, optional = true }
3939
strum = { version = "0.25", default-features = false }
4040
serde = { version = "1.0", default-features = false }

issues/1473/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[workspace]
2+
# A separate workspace
3+
4+
[package]
5+
name = "sea-orm-issues-1473"
6+
version = "0.1.0"
7+
edition = "2021"
8+
publish = false
9+
10+
[dependencies.sea-orm]
11+
path = "../../"
12+
default-features = false
13+
features = ["macros", "runtime-tokio-native-tls"]

issues/1473/src/main.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use sea_orm::{sea_query::{self, Iden}};
2+
3+
#[derive(Iden)]
4+
enum Character {
5+
Table,
6+
Id,
7+
}
8+
9+
#[derive(Iden)]
10+
struct Glyph;
11+
12+
fn main() {
13+
assert_eq!(Character::Table.to_string(), "character");
14+
assert_eq!(Character::Id.to_string(), "id");
15+
16+
assert_eq!(Glyph.to_string(), "glyph");
17+
}
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use heck::ToSnakeCase;
2+
use proc_macro2::{self, TokenStream};
3+
use quote::{quote, quote_spanned};
4+
use syn::{
5+
punctuated::Punctuated, DataEnum, DataStruct, DeriveInput, Expr, Fields, LitStr, Variant,
6+
};
7+
8+
fn must_be_valid_iden(name: &str) -> bool {
9+
// can only begin with [a-z_]
10+
name.chars()
11+
.take(1)
12+
.all(|c| c == '_' || c.is_ascii_alphabetic())
13+
&& name.chars().all(|c| c == '_' || c.is_ascii_alphanumeric())
14+
}
15+
16+
fn impl_iden_for_unit_struct(
17+
ident: &proc_macro2::Ident,
18+
new_iden: &str,
19+
) -> proc_macro2::TokenStream {
20+
let prepare = if must_be_valid_iden(new_iden) {
21+
quote! {
22+
fn prepare(&self, s: &mut dyn ::std::fmt::Write, q: sea_orm::sea_query::Quote) {
23+
write!(s, "{}", q.left()).unwrap();
24+
self.unquoted(s);
25+
write!(s, "{}", q.right()).unwrap();
26+
}
27+
}
28+
} else {
29+
quote! {}
30+
};
31+
quote! {
32+
impl sea_orm::sea_query::Iden for #ident {
33+
#prepare
34+
35+
fn unquoted(&self, s: &mut dyn ::std::fmt::Write) {
36+
write!(s, #new_iden).unwrap();
37+
}
38+
}
39+
}
40+
}
41+
42+
fn impl_iden_for_enum(
43+
ident: &proc_macro2::Ident,
44+
variants: Punctuated<Variant, syn::token::Comma>,
45+
) -> proc_macro2::TokenStream {
46+
let variants = variants.iter();
47+
let mut all_valid = true;
48+
49+
let match_pair: Vec<TokenStream> = variants
50+
.map(|v| {
51+
let var_ident = &v.ident;
52+
let mut var_name = var_ident.to_string().to_snake_case();
53+
v.attrs
54+
.iter()
55+
.filter(|attr| attr.path().is_ident("sea_orm"))
56+
.try_for_each(|attr| {
57+
attr.parse_nested_meta(|meta| {
58+
if meta.path.is_ident("iden") {
59+
let litstr: LitStr = meta.value()?.parse()?;
60+
var_name = litstr.value();
61+
all_valid &= must_be_valid_iden(var_name.as_str());
62+
} else {
63+
// Reads the value expression to advance the parse stream.
64+
// Some parameters do not have any value,
65+
// so ignoring an error occurred here.
66+
let _: Option<Expr> = meta.value().and_then(|v| v.parse()).ok();
67+
}
68+
Ok(())
69+
})
70+
})
71+
.expect("something something");
72+
quote! { Self::#var_ident => write!(s, "{}", #var_name).unwrap() }
73+
})
74+
.collect();
75+
76+
let match_arms: TokenStream = quote! { #(#match_pair),* };
77+
78+
let prepare = if all_valid {
79+
quote! {
80+
fn prepare(&self, s: &mut dyn ::std::fmt::Write, q: sea_orm::sea_query::Quote) {
81+
write!(s, "{}", q.left()).unwrap();
82+
self.unquoted(s);
83+
write!(s, "{}", q.right()).unwrap();
84+
}
85+
}
86+
} else {
87+
quote! {}
88+
};
89+
90+
quote! {
91+
impl sea_orm::sea_query::Iden for #ident {
92+
#prepare
93+
94+
fn unquoted(&self, s: &mut dyn ::std::fmt::Write) {
95+
match self {
96+
#match_arms
97+
};
98+
}
99+
}
100+
}
101+
}
102+
103+
pub fn expand_derive_iden(input: DeriveInput) -> syn::Result<TokenStream> {
104+
let DeriveInput { ident, data, .. } = input;
105+
106+
let mut new_iden: TokenStream = ident.to_string().to_snake_case().parse().unwrap();
107+
input
108+
.attrs
109+
.iter()
110+
.filter(|attr| attr.path().is_ident("sea_orm"))
111+
.try_for_each(|attr| {
112+
attr.parse_nested_meta(|meta| {
113+
if meta.path.is_ident("iden") {
114+
let litstr: LitStr = meta.value()?.parse()?;
115+
new_iden = syn::parse_str::<TokenStream>(&litstr.value())?;
116+
} else {
117+
// Reads the value expression to advance the parse stream.
118+
// Some parameters do not have any value,
119+
// so ignoring an error occurred here.
120+
let _: Option<Expr> = meta.value().and_then(|v| v.parse()).ok();
121+
}
122+
Ok(())
123+
})
124+
})?;
125+
126+
// Currently we only support enums and unit structs
127+
match data {
128+
syn::Data::Enum(DataEnum { variants, .. }) => {
129+
if variants.is_empty() {
130+
Ok(TokenStream::new())
131+
} else {
132+
Ok(impl_iden_for_enum(&ident, variants))
133+
}
134+
}
135+
syn::Data::Struct(DataStruct {
136+
fields: Fields::Unit,
137+
..
138+
}) => Ok(impl_iden_for_unit_struct(
139+
&ident,
140+
new_iden.to_string().as_str(),
141+
)),
142+
_ => Ok(quote_spanned! {
143+
ident.span() => compile_error!("you can only derive DeriveIden on unit struct or enum");
144+
}),
145+
}
146+
}

sea-orm-macros/src/derives/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod active_model;
44
mod active_model_behavior;
55
mod attributes;
66
mod column;
7+
mod derive_iden;
78
mod entity;
89
mod entity_model;
910
mod from_query_result;
@@ -24,6 +25,7 @@ pub use active_enum_display::*;
2425
pub use active_model::*;
2526
pub use active_model_behavior::*;
2627
pub use column::*;
28+
pub use derive_iden::*;
2729
pub use entity::*;
2830
pub use entity_model::*;
2931
pub use from_query_result::*;

sea-orm-macros/src/lib.rs

+42
Original file line numberDiff line numberDiff line change
@@ -852,3 +852,45 @@ pub fn derive_active_enum_display(input: TokenStream) -> TokenStream {
852852
Err(e) => e.to_compile_error().into(),
853853
}
854854
}
855+
856+
/// The DeriveIden derive macro will implement `sea_orm::sea_query::Iden` for simplify Iden implementation.
857+
///
858+
/// ## Usage
859+
///
860+
/// ```rust
861+
/// use sea_orm::DeriveIden;
862+
///
863+
/// #[derive(DeriveIden)]
864+
/// pub enum Class {
865+
/// Id,
866+
/// Title,
867+
/// Text,
868+
/// }
869+
///
870+
/// #[derive(DeriveIden)]
871+
/// struct Glyph;
872+
/// ```
873+
///
874+
/// You can use iden = "" to customize the name
875+
/// ```
876+
/// use sea_orm::DeriveIden;
877+
///
878+
/// #[derive(DeriveIden)]
879+
/// pub enum Class {
880+
/// Id,
881+
/// #[sea_orm(iden = "turtle")]
882+
/// Title,
883+
/// #[sea_orm(iden = "TeXt")]
884+
/// Text,
885+
/// }
886+
/// ```
887+
#[cfg(feature = "derive")]
888+
#[proc_macro_derive(DeriveIden, attributes(sea_orm))]
889+
pub fn derive_iden(input: TokenStream) -> TokenStream {
890+
let derive_input = parse_macro_input!(input as DeriveInput);
891+
892+
match derives::expand_derive_iden(derive_input) {
893+
Ok(token_stream) => token_stream.into(),
894+
Err(e) => e.to_compile_error().into(),
895+
}
896+
}

src/entity/column.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{EntityName, IdenStatic, IntoSimpleExpr, Iterable};
1+
use crate::{self as sea_orm, EntityName, IdenStatic, IntoSimpleExpr, Iterable};
22
use sea_query::{
33
Alias, BinOper, DynIden, Expr, Iden, IntoIden, SeaRc, SelectStatement, SimpleExpr, Value,
44
};

src/lib.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -349,15 +349,13 @@ pub use schema::*;
349349
#[cfg(feature = "macros")]
350350
pub use sea_orm_macros::{
351351
DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn,
352-
DeriveCustomColumn, DeriveDisplay, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel,
353-
DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey, DeriveRelatedEntity,
354-
DeriveRelation, FromJsonQueryResult, FromQueryResult,
352+
DeriveCustomColumn, DeriveDisplay, DeriveEntity, DeriveEntityModel, DeriveIden,
353+
DeriveIntoActiveModel, DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey,
354+
DeriveRelatedEntity, DeriveRelation, FromJsonQueryResult, FromQueryResult,
355355
};
356356

357357
pub use sea_query;
358358
pub use sea_query::Iden;
359-
#[cfg(feature = "macros")]
360-
pub use sea_query::Iden as DeriveIden;
361359

362360
pub use sea_orm_macros::EnumIter;
363361
pub use strum;

tests/derive_iden_tests.rs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
pub mod common;
2+
pub use common::{features::*, setup::*, TestContext};
3+
use sea_orm::entity::prelude::*;
4+
use sea_orm_macros::DeriveIden;
5+
6+
#[derive(DeriveIden)]
7+
pub enum Class {
8+
Id,
9+
Title,
10+
Text,
11+
}
12+
13+
#[derive(DeriveIden)]
14+
pub enum Book {
15+
Id,
16+
#[sea_orm(iden = "turtle")]
17+
Title,
18+
#[sea_orm(iden = "TeXt")]
19+
Text,
20+
#[sea_orm(iden = "ty_pe")]
21+
Type,
22+
}
23+
24+
#[derive(DeriveIden)]
25+
struct Glyph;
26+
27+
#[derive(DeriveIden)]
28+
#[sea_orm(iden = "weRd")]
29+
struct Word;
30+
31+
#[test]
32+
fn main() -> Result<(), DbErr> {
33+
assert_eq!(Class::Id.to_string(), "id");
34+
assert_eq!(Class::Title.to_string(), "title");
35+
assert_eq!(Class::Text.to_string(), "text");
36+
37+
assert_eq!(Book::Id.to_string(), "id");
38+
assert_eq!(Book::Title.to_string(), "turtle");
39+
assert_eq!(Book::Text.to_string(), "TeXt");
40+
assert_eq!(Book::Type.to_string(), "ty_pe");
41+
42+
assert_eq!(Glyph.to_string(), "glyph");
43+
44+
assert_eq!(Word.to_string(), "weRd");
45+
Ok(())
46+
}

0 commit comments

Comments
 (0)