diff --git a/src/attributes.rs b/src/attributes.rs index b4e4c6d..90356a4 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -65,7 +65,7 @@ where } /// Represents a rename attribute for an enum variant. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] struct VariantRename(String); impl TryFrom<(String, String)> for VariantRename { @@ -98,7 +98,7 @@ impl VariantRename { } /// Represents attribute configurations for renaming enum variants. -#[derive(Default)] +#[derive(Default, Debug, PartialEq)] pub(crate) struct Attributes { case: Option, prefix: Option, @@ -211,3 +211,205 @@ impl Variants { .collect() } } + +#[cfg(test)] +mod tests { + use quote::quote; + + use super::*; + + #[test] + fn test_parse_string() { + assert_eq!(parse_string("\"hello\""), Ok("hello".to_string())); + assert_eq!( + parse_string("hello"), + Err("String must be enclosed in double quotes") + ); + + assert_eq!( + parse_string("\"hello"), + Err("String must be enclosed in double quotes") + ); + assert_eq!( + parse_string("hello\""), + Err("String must be enclosed in double quotes") + ); + assert_eq!(parse_string("\"he\"llo\""), Ok("he\"llo".to_string())); + + assert_eq!(parse_string("\"\""), Ok("".to_string())); + assert_eq!(parse_string("\"\"\""), Ok("\"".to_string())); + } + + fn assert_parse_token_list(tokens: TokenStream, expected: Vec) + where + T: TryFrom<(String, String)> + PartialEq + std::fmt::Debug, + { + let result = parse_token_list::(&tokens).unwrap(); + assert_eq!(result, expected); + } + + #[test] + fn test_parse_token_list_string_string() { + assert_parse_token_list::<(String, String)>(quote! {}, vec![]); + + assert_parse_token_list::<(String, String)>( + quote! { rename = "hello" }, + vec![("rename".to_string(), "\"hello\"".to_string())], + ); + + assert_parse_token_list( + quote! { rename = "hello", rename = "world" }, + vec![ + ("rename".to_string(), "\"hello\"".to_string()), + ("rename".to_string(), "\"world\"".to_string()), + ], + ); + } + + #[derive(Debug, PartialEq)] + struct TestStruct(String); + + impl TryFrom<(String, String)> for TestStruct { + type Error = &'static str; + + fn try_from(value: (String, String)) -> Result { + Ok(Self(parse_string(value.1.as_str())?)) + } + } + + #[test] + fn test_parse_token_list_struct() { + assert_parse_token_list::( + quote! { rename = "hello" }, + vec![TestStruct("hello".to_string())], + ); + + assert_parse_token_list::( + quote! { rename = "hello", rename = "world" }, + vec![ + TestStruct("hello".to_string()), + TestStruct("world".to_string()), + ], + ); + } + + #[test] + fn test_variant_rename_try_from() { + assert_eq!( + VariantRename::try_from(("rename".to_string(), "\"hello\"".to_string())), + Ok(VariantRename("hello".to_string())) + ); + + assert_eq!( + VariantRename::try_from(("rename".to_string(), "hello".to_string())), + Err("String must be enclosed in double quotes") + ); + + assert_eq!( + VariantRename::try_from(("not_rename".to_string(), "\"hello\"".to_string())), + Err("Not a rename string") + ); + } + + #[test] + fn test_parse_token_list_variant_rename() { + assert_parse_token_list::( + quote! { rename = "hello" }, + vec![VariantRename("hello".to_string())], + ); + + assert_parse_token_list::( + quote! { rename = "hello", rename = "world" }, + vec![ + VariantRename("hello".to_string()), + VariantRename("world".to_string()), + ], + ); + } + + #[test] + fn test_attributes_parse_args() { + let attribute = + syn::parse_quote! { #[enum_stringify(prefix = "pre", suffix = "suf", case = "snake")] }; + let attributes = Attributes::parse_args(&attribute).unwrap(); + assert_eq!(attributes.prefix, Some("pre".to_string())); + assert_eq!(attributes.suffix, Some("suf".to_string())); + assert_eq!( + attributes.case.map(|a| a.to_string()), + Some("snake".to_string()) + ); + + let attribute = syn::parse_quote! { #[enum_stringify(prefix = "pre", suffix = "suf")] }; + let attributes = Attributes::parse_args(&attribute).unwrap(); + assert_eq!(attributes.prefix, Some("pre".to_string())); + assert_eq!(attributes.suffix, Some("suf".to_string())); + assert_eq!(attributes.case, None); + + let attribute = syn::parse_quote! { #[enum_stringify(case = "snake")] }; + let attributes = Attributes::parse_args(&attribute).unwrap(); + assert_eq!(attributes.prefix, None); + assert_eq!(attributes.suffix, None); + assert_eq!( + attributes.case.map(|a| a.to_string()), + Some("snake".to_string()) + ); + + let attribute = syn::parse_quote! { #[enum_stringify] }; + assert_eq!(Attributes::parse_args(&attribute), None); + } + + #[test] + fn test_attributes_update_attribute() { + let mut attributes = Attributes::default(); + attributes.update_attribute(("prefix".to_string(), "\"pre\"".to_string())); + assert_eq!(attributes.prefix, Some("pre".to_string())); + + attributes.update_attribute(("suffix".to_string(), "\"suf\"".to_string())); + assert_eq!(attributes.suffix, Some("suf".to_string())); + + attributes.update_attribute(("case".to_string(), "\"snake\"".to_string())); + assert_eq!( + attributes.case.clone().map(|a| a.to_string()), + Some("snake".to_string()) + ); + + attributes.update_attribute(("invalid".to_string(), "\"value\"".to_string())); + assert_eq!(attributes.prefix, Some("pre".to_string())); + assert_eq!(attributes.suffix, Some("suf".to_string())); + assert_eq!( + attributes.case.clone().map(|a| a.to_string()), + Some("snake".to_string()) + ); + + attributes.update_attribute(("prefix".to_string(), "\"new1\"".to_string())); + assert_eq!(attributes.prefix, Some("new1".to_string())); + + attributes.update_attribute(("suffix".to_string(), "\"new2\"".to_string())); + assert_eq!(attributes.suffix, Some("new2".to_string())); + + attributes.update_attribute(("case".to_string(), "\"upper\"".to_string())); + assert_eq!( + attributes.case.clone().map(|a| a.to_string()), + Some("upper".to_string()) + ); + } + + #[test] + fn test_attributes_rename() { + let mut attributes = Attributes { + prefix: Some("pre".to_string()), + suffix: Some("suf".to_string()), + case: None, + }; + + assert_eq!(attributes.rename("name"), "prenamesuf"); + assert_eq!(attributes.rename("Name"), "preNamesuf"); + assert_eq!(attributes.rename("NAME"), "preNAMEsuf"); + + attributes.update_attribute(("case".to_string(), "\"upper_flat\"".to_string())); + + assert_eq!(attributes.rename("name"), "PRENAMESUF"); + assert_eq!(attributes.rename("Name"), "PRENAMESUF"); + assert_eq!(attributes.rename("NAME"), "PRENAMESUF"); + } +} diff --git a/src/case.rs b/src/case.rs index 89927e4..54c3f22 100644 --- a/src/case.rs +++ b/src/case.rs @@ -1,6 +1,9 @@ +use std::fmt::Display; + use convert_case::Casing; /// Wrapper struct around `convert_case::Case` to represent different casing styles. +#[derive(Debug, PartialEq, Clone)] pub(crate) struct Case(convert_case::Case); // This is used to check if the first string is "case" and then attempt conversion of the second string. @@ -44,6 +47,31 @@ impl TryFrom for Case { } } +impl Display for Case { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let v = match self.0 { + convert_case::Case::Upper => "upper".to_string(), + convert_case::Case::Lower => "lower".to_string(), + convert_case::Case::Title => "title".to_string(), + convert_case::Case::Toggle => "toggle".to_string(), + convert_case::Case::Camel => "camel".to_string(), + convert_case::Case::Pascal => "pascal".to_string(), + convert_case::Case::UpperCamel => "upper_camel".to_string(), + convert_case::Case::Snake => "snake".to_string(), + convert_case::Case::UpperSnake => "upper_snake".to_string(), + convert_case::Case::ScreamingSnake => "screaming_snake".to_string(), + convert_case::Case::Kebab => "kebab".to_string(), + convert_case::Case::Cobol => "cobol".to_string(), + convert_case::Case::UpperKebab => "upper_kebab".to_string(), + convert_case::Case::Train => "train".to_string(), + convert_case::Case::Flat => "flat".to_string(), + convert_case::Case::UpperFlat => "upper_flat".to_string(), + convert_case::Case::Alternating => "alternating".to_string(), + }; + write!(f, "{v}") + } +} + impl Case { /// Applies the stored casing style to the given string `s` and returns the formatted result. pub(crate) fn to_case(&self, s: &str) -> String {