diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index d09774280..5b72943a8 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -1,9 +1,11 @@ -use super::util::camel_case_with_escaped_non_uax31; -use crate::derives::enum_variant_rename::RenameRule; use heck::ToUpperCamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; -use syn::{parse, Expr, Lit, LitInt, LitStr, UnOp}; +use syn::{Expr, Lit, LitInt, LitStr, parse, UnOp}; + +use crate::strum::helpers::case_style::{CaseStyle, CaseStyleHelpers}; + +use super::util::camel_case_with_escaped_non_uax31; enum Error { InputNotEnum, @@ -18,14 +20,14 @@ struct ActiveEnum { db_type: TokenStream, is_string: bool, variants: Vec, - rename_all: Option, + rename_all: Option, } struct ActiveEnumVariant { ident: syn::Ident, string_value: Option, num_value: Option, - rename: Option, + rename: Option, } impl ActiveEnum { @@ -225,8 +227,7 @@ impl ActiveEnum { } else if let Some(num_value) = &variant.num_value { quote! { #num_value } } else if let Some(rename_rule) = variant.rename.or(*rename_all) { - let variant_ident = variant.ident.to_string(); - let variant_ident = rename_rule.apply_to_variant(&variant_ident); + let variant_ident = variant.ident.convert_case(Some(rename_rule)); quote! { #variant_ident } } else { diff --git a/sea-orm-macros/src/derives/enum_variant_rename.rs b/sea-orm-macros/src/derives/enum_variant_rename.rs deleted file mode 100644 index 864794c39..000000000 --- a/sea-orm-macros/src/derives/enum_variant_rename.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::fmt; -use std::fmt::{Debug, Display}; -use syn::meta::ParseNestedMeta; -use syn::Error; -use syn::LitStr; -use RenameRule::*; - -#[derive(Copy, Clone, PartialEq)] -pub enum RenameRule { - /// Rename direct children to "lowercase" style. - LowerCase, - /// Rename direct children to "UPPERCASE" style. - UpperCase, - /// Rename direct children to "PascalCase" style, as typically used for - /// enum variants. - PascalCase, - /// Rename direct children to "camelCase" style. - CamelCase, - /// Rename direct children to "snake_case" style, as commonly used for - /// fields. - SnakeCase, - /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly - /// used for constants. - ScreamingSnakeCase, - /// Rename direct children to "kebab-case" style. - KebabCase, - /// Rename direct children to "SCREAMING-KEBAB-CASE" style. - ScreamingKebabCase, -} - -static RENAME_RULES: &[(&str, RenameRule)] = &[ - ("lowercase", LowerCase), - ("UPPERCASE", UpperCase), - ("PascalCase", PascalCase), - ("camelCase", CamelCase), - ("snake_case", SnakeCase), - ("SCREAMING_SNAKE_CASE", ScreamingSnakeCase), - ("kebab-case", KebabCase), - ("SCREAMING-KEBAB-CASE", ScreamingKebabCase), -]; - -impl RenameRule { - pub fn from_str(rename_all_str: &str) -> Result { - for (name, rule) in RENAME_RULES { - if rename_all_str == *name { - return Ok(*rule); - } - } - Err(ParseError { - unknown: rename_all_str, - }) - } - - /// Apply a renaming rule to an enum variant, returning the version expected in the source. - pub fn apply_to_variant(self, variant: &str) -> String { - match self { - PascalCase => variant.to_owned(), - LowerCase => variant.to_ascii_lowercase(), - UpperCase => variant.to_ascii_uppercase(), - CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..], - SnakeCase => { - let mut snake = String::new(); - for (i, ch) in variant.char_indices() { - if i > 0 && ch.is_uppercase() { - snake.push('_'); - } - snake.push(ch.to_ascii_lowercase()); - } - snake - } - ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(), - KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"), - ScreamingKebabCase => ScreamingSnakeCase - .apply_to_variant(variant) - .replace('_', "-"), - } - } - - /// Apply a renaming rule to a struct field, returning the version expected in the source. - pub fn apply_to_field(self, field: &str) -> String { - match self { - LowerCase | SnakeCase => field.to_owned(), - UpperCase => field.to_ascii_uppercase(), - PascalCase => { - let mut pascal = String::new(); - let mut capitalize = true; - for ch in field.chars() { - if ch == '_' { - capitalize = true; - } else if capitalize { - pascal.push(ch.to_ascii_uppercase()); - capitalize = false; - } else { - pascal.push(ch); - } - } - pascal - } - CamelCase => { - let pascal = PascalCase.apply_to_field(field); - pascal[..1].to_ascii_lowercase() + &pascal[1..] - } - ScreamingSnakeCase => field.to_ascii_uppercase(), - KebabCase => field.replace('_', "-"), - ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"), - } - } -} - -pub struct ParseError<'a> { - unknown: &'a str, -} - -impl<'a> Display for ParseError<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("unknown rename rule `rename_all = ")?; - Debug::fmt(self.unknown, f)?; - f.write_str("`, expected one of ")?; - for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() { - if i > 0 { - f.write_str(", ")?; - } - Debug::fmt(name, f)?; - } - Ok(()) - } -} - -impl<'meta> TryFrom<&ParseNestedMeta<'meta>> for RenameRule { - type Error = Error; - - fn try_from(value: &ParseNestedMeta) -> Result { - let meta_string_literal: LitStr = value.value()?.parse()?; - match RenameRule::from_str(meta_string_literal.value().as_str()) { - Ok(rule) => Ok(rule), - Err(error) => Err(value.error(format!( - "Unknown value for attribute parameter `rename_all`: {error}", - ))), - } - } -} diff --git a/sea-orm-macros/src/derives/mod.rs b/sea-orm-macros/src/derives/mod.rs index 6b37c7681..fac2956fd 100644 --- a/sea-orm-macros/src/derives/mod.rs +++ b/sea-orm-macros/src/derives/mod.rs @@ -1,3 +1,22 @@ +pub use active_enum::*; +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::*; +pub use into_active_model::*; +pub use migration::*; +pub use model::*; +pub use partial_model::*; +pub use primary_key::*; +pub use related_entity::*; +pub use relation::*; +pub use try_getable_from_json::*; +pub use value_type::*; + mod active_enum; mod active_enum_display; mod active_model; @@ -7,7 +26,6 @@ mod column; mod derive_iden; mod entity; mod entity_model; -mod enum_variant_rename; mod from_query_result; mod into_active_model; mod migration; @@ -20,22 +38,3 @@ mod sql_type_match; mod try_getable_from_json; mod util; mod value_type; - -pub use active_enum::*; -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::*; -pub use into_active_model::*; -pub use migration::*; -pub use model::*; -pub use partial_model::*; -pub use primary_key::*; -pub use related_entity::*; -pub use relation::*; -pub use try_getable_from_json::*; -pub use value_type::*; diff --git a/sea-orm-macros/src/strum/helpers/case_style.rs b/sea-orm-macros/src/strum/helpers/case_style.rs index 425382606..ca1fade63 100644 --- a/sea-orm-macros/src/strum/helpers/case_style.rs +++ b/sea-orm-macros/src/strum/helpers/case_style.rs @@ -1,7 +1,9 @@ +use std::str::FromStr; + use heck::{ ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase, }; -use std::str::FromStr; +use syn::meta::ParseNestedMeta; use syn::{ parse::{Parse, ParseStream}, Ident, LitStr, @@ -24,15 +26,15 @@ pub enum CaseStyle { const VALID_CASE_STYLES: &[&str] = &[ "camelCase", - "PascalCase", "kebab-case", - "snake_case", + "mixed_case", "SCREAMING_SNAKE_CASE", - "SCREAMING-KEBAB-CASE", - "lowercase", - "UPPERCASE", + "snake_case", "title_case", - "mixed_case", + "UPPERCASE", + "lowercase", + "SCREAMING-KEBAB-CASE", + "PascalCase", ]; impl Parse for CaseStyle { @@ -109,6 +111,21 @@ impl CaseStyleHelpers for Ident { } } +impl<'meta> TryFrom<&ParseNestedMeta<'meta>> for CaseStyle { + type Error = syn::Error; + + fn try_from(value: &ParseNestedMeta) -> Result { + let meta_string_literal: LitStr = value.value()?.parse()?; + let value_string = meta_string_literal.value(); + match CaseStyle::from_str(value_string.as_str()) { + Ok(rule) => Ok(rule), + Err(()) => Err(value.error(format!( + "Unknown value for attribute parameter: `{value_string}`. Valid values are: `{VALID_CASE_STYLES:?}`" + ))), + } + } +} + #[test] fn test_convert_case() { let id = Ident::new("test_me", proc_macro2::Span::call_site()); diff --git a/sea-orm-macros/tests/derive_active_enum_test.rs b/sea-orm-macros/tests/derive_active_enum_test.rs new file mode 100644 index 000000000..806d0cc80 --- /dev/null +++ b/sea-orm-macros/tests/derive_active_enum_test.rs @@ -0,0 +1,109 @@ +use sea_orm::ActiveEnum; +use sea_orm_macros::{DeriveActiveEnum, EnumIter}; + +#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] +#[sea_orm( + rs_type = "String", + db_type = "Enum", + enum_name = "test_enum", + rename_all = "camelCase" +)] +enum TestEnum { + DefaultVariant, + #[sea_orm(rename = "camelCase")] + VariantCamelCase, + #[sea_orm(rename = "kebab-case")] + VariantKebabCase, + #[sea_orm(rename = "mixed_case")] + VariantMixedCase, + #[sea_orm(rename = "SCREAMING_SNAKE_CASE")] + VariantShoutySnakeCase, + #[sea_orm(rename = "snake_case")] + VariantSnakeCase, + #[sea_orm(rename = "title_case")] + VariantTitleCase, + #[sea_orm(rename = "UPPERCASE")] + VariantUpperCase, + #[sea_orm(rename = "lowercase")] + VariantLowerCase, + #[sea_orm(rename = "SCREAMING-KEBAB-CASE")] + VariantScreamingKebabCase, + #[sea_orm(rename = "PascalCase")] + VariantPascalCase, + #[sea_orm(string_value = "CuStOmStRiNgVaLuE")] + CustomStringValue, +} + +#[test] +fn derive_active_enum_value() { + assert_eq!(TestEnum::DefaultVariant.to_value(), "defaultVariant"); + assert_eq!(TestEnum::VariantCamelCase.to_value(), "variantCamelCase"); + assert_eq!(TestEnum::VariantKebabCase.to_value(), "variant-kebab-case"); + assert_eq!(TestEnum::VariantMixedCase.to_value(), "variantMixedCase"); + assert_eq!( + TestEnum::VariantShoutySnakeCase.to_value(), + "VARIANT_SHOUTY_SNAKE_CASE" + ); + assert_eq!(TestEnum::VariantSnakeCase.to_value(), "variant_snake_case"); + assert_eq!(TestEnum::VariantTitleCase.to_value(), "Variant Title Case"); + assert_eq!(TestEnum::VariantUpperCase.to_value(), "VARIANTUPPERCASE"); + assert_eq!(TestEnum::VariantLowerCase.to_value(), "variantlowercase"); + assert_eq!( + TestEnum::VariantScreamingKebabCase.to_value(), + "VARIANT-SCREAMING-KEBAB-CASE" + ); + assert_eq!(TestEnum::VariantPascalCase.to_value(), "VariantPascalCase"); + assert_eq!(TestEnum::CustomStringValue.to_value(), "CuStOmStRiNgVaLuE"); +} + +#[test] +fn derive_active_enum_from_value() { + assert_eq!( + TestEnum::try_from_value(&"defaultVariant".to_string()), + Ok(TestEnum::DefaultVariant) + ); + assert_eq!( + TestEnum::try_from_value(&"variantCamelCase".to_string()), + Ok(TestEnum::VariantCamelCase) + ); + assert_eq!( + TestEnum::try_from_value(&"variant-kebab-case".to_string()), + Ok(TestEnum::VariantKebabCase) + ); + assert_eq!( + TestEnum::try_from_value(&"variantMixedCase".to_string()), + Ok(TestEnum::VariantMixedCase) + ); + assert_eq!( + TestEnum::try_from_value(&"VARIANT_SHOUTY_SNAKE_CASE".to_string()), + Ok(TestEnum::VariantShoutySnakeCase), + ); + assert_eq!( + TestEnum::try_from_value(&"variant_snake_case".to_string()), + Ok(TestEnum::VariantSnakeCase) + ); + assert_eq!( + TestEnum::try_from_value(&"Variant Title Case".to_string()), + Ok(TestEnum::VariantTitleCase) + ); + assert_eq!( + TestEnum::try_from_value(&"VARIANTUPPERCASE".to_string()), + Ok(TestEnum::VariantUpperCase) + ); + assert_eq!( + TestEnum::try_from_value(&"variantlowercase".to_string()), + Ok(TestEnum::VariantLowerCase) + ); + assert_eq!( + TestEnum::try_from_value(&"VARIANT-SCREAMING-KEBAB-CASE".to_string()), + Ok(TestEnum::VariantScreamingKebabCase), + ); + assert_eq!( + TestEnum::try_from_value(&"VariantPascalCase".to_string()), + Ok(TestEnum::VariantPascalCase) + ); + assert_eq!( + TestEnum::try_from_value(&"CuStOmStRiNgVaLuE".to_string()), + Ok(TestEnum::CustomStringValue) + ); +} diff --git a/sea-orm-macros/tests/derive_value_type_test.rs b/sea-orm-macros/tests/derive_value_type_test.rs index d719b37d1..b1de7f15e 100644 --- a/sea-orm-macros/tests/derive_value_type_test.rs +++ b/sea-orm-macros/tests/derive_value_type_test.rs @@ -1,5 +1,3 @@ -use sea_orm_macros::DeriveActiveEnum; - #[test] fn when_user_import_nothing_macro_still_works_test() { #[derive(sea_orm::DeriveValueType)] @@ -13,16 +11,3 @@ fn when_user_alias_result_macro_still_works_test() { #[derive(sea_orm::DeriveValueType)] struct MyString(String); } - -#[derive(DeriveActiveEnum)] -#[sea_orm( - rs_type = "String", - db_type = "Enum", - enum_name = "test_enum", - rename_all = "camelCase" -)] -pub enum TestEnum { - Variant, - #[sea_orm(rename = "PascalCase")] - AnotherVariant, -}