From 46a4cafaa9d9979bd37e24eb1b277244e706ae69 Mon Sep 17 00:00:00 2001 From: Ari Seyhun Date: Wed, 18 Aug 2021 15:20:33 +0930 Subject: [PATCH 1/5] Implement `FromStr` for `DeriveColumn` --- sea-orm-macros/src/derives/column.rs | 45 ++++++++++++++++++++++++++-- src/entity/column.rs | 25 ++++++++++++++++ src/query/combine.rs | 4 +-- src/query/update.rs | 2 +- src/tests_cfg/fruit.rs | 2 +- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/sea-orm-macros/src/derives/column.rs b/sea-orm-macros/src/derives/column.rs index 034e966d..0315fa1e 100644 --- a/sea-orm-macros/src/derives/column.rs +++ b/sea-orm-macros/src/derives/column.rs @@ -1,6 +1,6 @@ -use heck::SnakeCase; +use heck::{MixedCase, SnakeCase}; use proc_macro2::{Ident, TokenStream}; -use quote::{quote, quote_spanned}; +use quote::{format_ident, quote, quote_spanned}; use syn::{Data, DataEnum, Fields, Variant}; pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result { @@ -41,6 +41,44 @@ pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result syn::Result { + let parse_error_iden = format_ident!("Parse{}Err", ident); + + let data_enum = match data { + Data::Enum(data_enum) => data_enum, + _ => { + return Ok(quote_spanned! { + ident.span() => compile_error!("you can only derive DeriveColumn on enums"); + }) + } + }; + + let columns = data_enum.variants.iter().map(|column| { + let column_iden = column.ident.clone(); + let column_str_snake = column_iden.to_string().to_snake_case(); + let column_str_mixed = column_iden.to_string().to_mixed_case(); + quote!( + #column_str_snake | #column_str_mixed => Ok(#ident::#column_iden) + ) + }); + + Ok(quote!( + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct #parse_error_iden; + + impl std::str::FromStr for #ident { + type Err = #parse_error_iden; + + fn from_str(s: &str) -> Result { + match s { + #(#columns),*, + _ => Err(#parse_error_iden), + } + } + } + )) +} + pub fn expand_derive_column(ident: &Ident, data: &Data) -> syn::Result { let impl_iden = expand_derive_custom_column(ident, data)?; @@ -57,10 +95,13 @@ pub fn expand_derive_column(ident: &Ident, data: &Data) -> syn::Result syn::Result { let impl_default_as_str = impl_default_as_str(ident, data)?; + let impl_col_from_str = impl_col_from_str(ident, data)?; Ok(quote!( #impl_default_as_str + #impl_col_from_str + impl sea_orm::Iden for #ident { fn unquoted(&self, s: &mut dyn std::fmt::Write) { write!(s, "{}", self.as_str()).unwrap(); diff --git a/src/entity/column.rs b/src/entity/column.rs index 045a85ba..2456aba7 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -324,6 +324,7 @@ mod tests { tests_cfg::*, ColumnTrait, Condition, DbBackend, EntityTrait, QueryFilter, QueryTrait, }; use sea_query::Query; + use std::str::FromStr; #[test] fn test_in_subquery() { @@ -348,4 +349,28 @@ mod tests { .join(" ") ); } + + #[test] + fn test_col_from_str() { + match fruit::Column::from_str("id") { + Ok(col) => assert_eq!(col, fruit::Column::Id), + Err(_) => panic!("fruit from_str fails"), + } + match fruit::Column::from_str("name") { + Ok(col) => assert_eq!(col, fruit::Column::Name), + Err(_) => panic!("fruit from_str fails"), + } + match fruit::Column::from_str("cake_id") { + Ok(col) => assert_eq!(col, fruit::Column::CakeId), + Err(_) => panic!("fruit from_str fails"), + } + match fruit::Column::from_str("cakeId") { + Ok(col) => assert_eq!(col, fruit::Column::CakeId), + Err(_) => panic!("fruit from_str fails"), + } + match fruit::Column::from_str("does_not_exist") { + Ok(_) => panic!("fruit from_str found match when it shouldn't have"), + Err(err) => assert_eq!(err, fruit::ParseColumnErr), + } + } } diff --git a/src/query/combine.rs b/src/query/combine.rs index 8cce0510..71783e8b 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -167,7 +167,7 @@ mod tests { .left_join(fruit::Entity) .select_also(fruit::Entity) .filter(cake::Column::Id.eq(1)) - .filter(fruit::Column::Id.eq(2)) + .filter(ColumnTrait::eq(&fruit::Column::Id, 2)) .build(DbBackend::MySql) .to_string(), [ @@ -186,7 +186,7 @@ mod tests { .left_join(fruit::Entity) .select_with(fruit::Entity) .filter(cake::Column::Id.eq(1)) - .filter(fruit::Column::Id.eq(2)) + .filter(ColumnTrait::eq(&fruit::Column::Id, 2)) .build(DbBackend::MySql) .to_string(), [ diff --git a/src/query/update.rs b/src/query/update.rs index 21fd39cb..014f6ae1 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -233,7 +233,7 @@ mod tests { assert_eq!( Update::many(fruit::Entity) .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None))) - .filter(fruit::Column::Id.eq(2)) + .filter(ColumnTrait::eq(&fruit::Column::Id, 2)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."id" = 2"#, diff --git a/src/tests_cfg/fruit.rs b/src/tests_cfg/fruit.rs index 0511ae58..48d7fcfb 100644 --- a/src/tests_cfg/fruit.rs +++ b/src/tests_cfg/fruit.rs @@ -17,7 +17,7 @@ pub struct Model { pub cake_id: Option, } -#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +#[derive(Copy, Clone, PartialEq, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, From 332ab3ccf588798ffcdb53fca185ae869e6ca27c Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sun, 22 Aug 2021 22:15:40 +0800 Subject: [PATCH 2/5] Remove derive PartialEq --- sea-orm-macros/src/derives/column.rs | 2 +- src/entity/column.rs | 16 +++++++--------- src/query/combine.rs | 4 ++-- src/query/update.rs | 2 +- src/tests_cfg/fruit.rs | 2 +- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/sea-orm-macros/src/derives/column.rs b/sea-orm-macros/src/derives/column.rs index 0315fa1e..917dc669 100644 --- a/sea-orm-macros/src/derives/column.rs +++ b/sea-orm-macros/src/derives/column.rs @@ -63,7 +63,7 @@ pub fn impl_col_from_str(ident: &Ident, data: &Data) -> syn::Result }); Ok(quote!( - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy)] pub struct #parse_error_iden; impl std::str::FromStr for #ident { diff --git a/src/entity/column.rs b/src/entity/column.rs index 2456aba7..85100421 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -324,7 +324,6 @@ mod tests { tests_cfg::*, ColumnTrait, Condition, DbBackend, EntityTrait, QueryFilter, QueryTrait, }; use sea_query::Query; - use std::str::FromStr; #[test] fn test_in_subquery() { @@ -352,25 +351,24 @@ mod tests { #[test] fn test_col_from_str() { + use std::str::FromStr; + match fruit::Column::from_str("id") { - Ok(col) => assert_eq!(col, fruit::Column::Id), + Ok(col) => assert!(matches!(col, fruit::Column::Id)), Err(_) => panic!("fruit from_str fails"), } match fruit::Column::from_str("name") { - Ok(col) => assert_eq!(col, fruit::Column::Name), + Ok(col) => assert!(matches!(col, fruit::Column::Name)), Err(_) => panic!("fruit from_str fails"), } match fruit::Column::from_str("cake_id") { - Ok(col) => assert_eq!(col, fruit::Column::CakeId), + Ok(col) => assert!(matches!(col, fruit::Column::CakeId)), Err(_) => panic!("fruit from_str fails"), } match fruit::Column::from_str("cakeId") { - Ok(col) => assert_eq!(col, fruit::Column::CakeId), + Ok(col) => assert!(matches!(col, fruit::Column::CakeId)), Err(_) => panic!("fruit from_str fails"), } - match fruit::Column::from_str("does_not_exist") { - Ok(_) => panic!("fruit from_str found match when it shouldn't have"), - Err(err) => assert_eq!(err, fruit::ParseColumnErr), - } + assert!(matches!(fruit::Column::from_str("does_not_exist"), Err(_))); } } diff --git a/src/query/combine.rs b/src/query/combine.rs index 71783e8b..8cce0510 100644 --- a/src/query/combine.rs +++ b/src/query/combine.rs @@ -167,7 +167,7 @@ mod tests { .left_join(fruit::Entity) .select_also(fruit::Entity) .filter(cake::Column::Id.eq(1)) - .filter(ColumnTrait::eq(&fruit::Column::Id, 2)) + .filter(fruit::Column::Id.eq(2)) .build(DbBackend::MySql) .to_string(), [ @@ -186,7 +186,7 @@ mod tests { .left_join(fruit::Entity) .select_with(fruit::Entity) .filter(cake::Column::Id.eq(1)) - .filter(ColumnTrait::eq(&fruit::Column::Id, 2)) + .filter(fruit::Column::Id.eq(2)) .build(DbBackend::MySql) .to_string(), [ diff --git a/src/query/update.rs b/src/query/update.rs index 014f6ae1..21fd39cb 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -233,7 +233,7 @@ mod tests { assert_eq!( Update::many(fruit::Entity) .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None))) - .filter(ColumnTrait::eq(&fruit::Column::Id, 2)) + .filter(fruit::Column::Id.eq(2)) .build(DbBackend::Postgres) .to_string(), r#"UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."id" = 2"#, diff --git a/src/tests_cfg/fruit.rs b/src/tests_cfg/fruit.rs index 48d7fcfb..0511ae58 100644 --- a/src/tests_cfg/fruit.rs +++ b/src/tests_cfg/fruit.rs @@ -17,7 +17,7 @@ pub struct Model { pub cake_id: Option, } -#[derive(Copy, Clone, PartialEq, Debug, EnumIter, DeriveColumn)] +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] pub enum Column { Id, Name, From e5f290380a093f589fa883852c827b87e2c32514 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 23 Aug 2021 01:04:13 +0800 Subject: [PATCH 3/5] Use ColumnFromStrErr --- sea-orm-macros/src/derives/column.rs | 11 +++-------- src/error.rs | 11 +++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/sea-orm-macros/src/derives/column.rs b/sea-orm-macros/src/derives/column.rs index 917dc669..16f9bb22 100644 --- a/sea-orm-macros/src/derives/column.rs +++ b/sea-orm-macros/src/derives/column.rs @@ -1,6 +1,6 @@ use heck::{MixedCase, SnakeCase}; use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote, quote_spanned}; +use quote::{quote, quote_spanned}; use syn::{Data, DataEnum, Fields, Variant}; pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result { @@ -42,8 +42,6 @@ pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result syn::Result { - let parse_error_iden = format_ident!("Parse{}Err", ident); - let data_enum = match data { Data::Enum(data_enum) => data_enum, _ => { @@ -63,16 +61,13 @@ pub fn impl_col_from_str(ident: &Ident, data: &Data) -> syn::Result }); Ok(quote!( - #[derive(Debug, Clone, Copy)] - pub struct #parse_error_iden; - impl std::str::FromStr for #ident { - type Err = #parse_error_iden; + type Err = sea_orm::ColumnFromStrErr; fn from_str(s: &str) -> Result { match s { #(#columns),*, - _ => Err(#parse_error_iden), + _ => Err(sea_orm::ColumnFromStrErr(format!("Failed to parse '{}' as `{}`", s, stringify!(#ident)))), } } } diff --git a/src/error.rs b/src/error.rs index eff99912..8a695dac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,3 +16,14 @@ impl std::fmt::Display for DbErr { } } } + +#[derive(Debug, Clone)] +pub struct ColumnFromStrErr(pub String); + +impl std::error::Error for ColumnFromStrErr {} + +impl std::fmt::Display for ColumnFromStrErr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0.as_str()) + } +} From 4c3bc32fd80cd90082e9f1682a5b24ecce1e495c Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 23 Aug 2021 01:11:36 +0800 Subject: [PATCH 4/5] Refactor test case --- src/entity/column.rs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 85100421..8ffb7275 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -353,22 +353,25 @@ mod tests { fn test_col_from_str() { use std::str::FromStr; - match fruit::Column::from_str("id") { - Ok(col) => assert!(matches!(col, fruit::Column::Id)), - Err(_) => panic!("fruit from_str fails"), - } - match fruit::Column::from_str("name") { - Ok(col) => assert!(matches!(col, fruit::Column::Name)), - Err(_) => panic!("fruit from_str fails"), - } - match fruit::Column::from_str("cake_id") { - Ok(col) => assert!(matches!(col, fruit::Column::CakeId)), - Err(_) => panic!("fruit from_str fails"), - } - match fruit::Column::from_str("cakeId") { - Ok(col) => assert!(matches!(col, fruit::Column::CakeId)), - Err(_) => panic!("fruit from_str fails"), - } - assert!(matches!(fruit::Column::from_str("does_not_exist"), Err(_))); + assert!(matches!( + fruit::Column::from_str("id"), + Ok(fruit::Column::Id) + )); + assert!(matches!( + fruit::Column::from_str("name"), + Ok(fruit::Column::Name) + )); + assert!(matches!( + fruit::Column::from_str("cake_id"), + Ok(fruit::Column::CakeId) + )); + assert!(matches!( + fruit::Column::from_str("cakeId"), + Ok(fruit::Column::CakeId) + )); + assert!(matches!( + fruit::Column::from_str("does_not_exist"), + Err(crate::ColumnFromStrErr(_)) + )); } } From 5966100244e8b327734773e4e8f3332d47ac283a Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Mon, 23 Aug 2021 01:16:00 +0800 Subject: [PATCH 5/5] ColumnTrait extends FromStr --- src/entity/column.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/entity/column.rs b/src/entity/column.rs index 8ffb7275..16546057 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use crate::{EntityName, IdenStatic, Iterable}; use sea_query::{DynIden, Expr, SeaRc, SelectStatement, SimpleExpr, Value}; @@ -77,7 +78,7 @@ macro_rules! bind_subquery_func { // LINT: when the operand value does not match column type /// Wrapper of the identically named method in [`sea_query::Expr`] -pub trait ColumnTrait: IdenStatic + Iterable { +pub trait ColumnTrait: IdenStatic + Iterable + FromStr { type EntityName: EntityName; fn def(&self) -> ColumnDef;