From f9f879e488a62eba9c54c8e713cc94cc9f20e96e Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 23 Jul 2026 23:23:56 +0800 Subject: feat(config): refactor default feature name parsing --- src/config.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++---- src/func.rs | 12 ++++++++ src/invoke.rs | 17 ++++++++++++ test/src/test_func.rs | 32 ++++++++++++++++++++++ test/src/test_select.rs | 25 +++++++++++++++++ 5 files changed, 153 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 06aa088..5962103 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,17 +22,78 @@ fn read_default_feature_name() -> String { Err(_) => return "async".to_string(), }; - let value: toml::Value = match content.parse() { - Ok(v) => v, - Err(_) => return "async".to_string(), - }; + parse_feature_name_from_toml(&content).unwrap_or_else(|| "async".to_string()) +} +/// Pure function: parse `default_feature_name` from TOML content. +/// Returns `None` if the key is missing, unreadable, or not a string. +fn parse_feature_name_from_toml(content: &str) -> Option { + let value: toml::Value = content.parse().ok()?; value .get("package") .and_then(|p| p.get("metadata")) .and_then(|m| m.get("might_be_async")) .and_then(|a| a.get("default_feature_name")) .and_then(|v| v.as_str()) - .unwrap_or("async") - .to_string() + .map(|s| s.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_custom_feature_name() { + let toml = r#" + [package] + [package.metadata.might_be_async] + default_feature_name = "my_async" + "#; + assert_eq!( + parse_feature_name_from_toml(toml), + Some("my_async".to_string()) + ); + } + + #[test] + fn missing_metadata_returns_none() { + let toml = "[package]\nname = \"foo\"\n"; + assert_eq!(parse_feature_name_from_toml(toml), None); + } + + #[test] + fn missing_might_be_async_section_returns_none() { + let toml = r#" + [package] + [package.metadata] + some_other_key = "value" + "#; + assert_eq!(parse_feature_name_from_toml(toml), None); + } + + #[test] + fn empty_string_returns_none() { + assert_eq!(parse_feature_name_from_toml(""), None); + } + + #[test] + fn invalid_toml_returns_none() { + assert_eq!(parse_feature_name_from_toml("not valid toml {{"), None); + } + + #[test] + fn value_is_not_string_returns_none() { + let toml = r#" + [package.metadata.might_be_async] + default_feature_name = 42 + "#; + assert_eq!(parse_feature_name_from_toml(toml), None); + } + + #[test] + fn env_default_returns_foo_async() { + // In `cargo test`, CARGO_MANIFEST_DIR is set to this crate's directory, + // whose Cargo.toml has `default_feature_name = "foo_async"`. + assert_eq!(default_feature_name(), "foo_async"); + } } diff --git a/src/func.rs b/src/func.rs index 5557992..a502e36 100644 --- a/src/func.rs +++ b/src/func.rs @@ -78,4 +78,16 @@ mod tests { let args: FuncArgs = syn::parse2(input).unwrap(); assert_eq!(args.feature_name, "custom_name"); } + + #[test] + fn integer_literal_is_rejected() { + let input: proc_macro2::TokenStream = "42".parse().unwrap(); + assert!(syn::parse2::(input).is_err()); + } + + #[test] + fn multiple_args_are_rejected() { + let input: proc_macro2::TokenStream = r#""a" "b""#.parse().unwrap(); + assert!(syn::parse2::(input).is_err()); + } } diff --git a/src/invoke.rs b/src/invoke.rs index f7da20a..2ada0b4 100644 --- a/src/invoke.rs +++ b/src/invoke.rs @@ -86,4 +86,21 @@ mod tests { _ => panic!("expected Explicit variant"), } } + + #[test] + fn explicit_missing_arrow_is_rejected() { + let input: proc_macro2::TokenStream = r#""ft" expr()"#.parse().unwrap(); + assert!(syn::parse2::(input).is_err()); + } + + #[test] + fn integer_parses_as_default_variant() { + // integer literal is not a LitStr, so it falls through to Default variant + let input: proc_macro2::TokenStream = "42".parse().unwrap(); + let args: InvokeArgs = syn::parse2(input).unwrap(); + match args { + InvokeArgs::Default(_) => {} // expected + _ => panic!("expected Default variant"), + } + } } diff --git a/test/src/test_func.rs b/test/src/test_func.rs index 71fe365..666a99f 100644 --- a/test/src/test_func.rs +++ b/test/src/test_func.rs @@ -34,3 +34,35 @@ fn triple(x: i32) -> i32 { fn test_func_custom_feature() { assert_eq!(triple(3), 9); } + +#[cfg(feature = "metadata_async")] +#[test] +fn test_func_async_identity() { + let result = futures::executor::block_on(async { identity(42).await }); + assert_eq!(result, 42); +} + +#[cfg(feature = "metadata_async")] +#[test] +fn test_func_async_generic() { + let r = futures::executor::block_on(async { first(10, 20).await }); + assert_eq!(r, 10); +} + +#[might_be_async::func] +fn greet_async(name: &str) -> String { + format!("Hello, {name}!") +} + +#[cfg(not(feature = "metadata_async"))] +#[test] +fn test_func_sync_greet() { + assert_eq!(greet_async("world"), "Hello, world!"); +} + +#[cfg(feature = "metadata_async")] +#[test] +fn test_func_async_greet() { + let r = futures::executor::block_on(greet_async("world")); + assert_eq!(r, "Hello, world!"); +} diff --git a/test/src/test_select.rs b/test/src/test_select.rs index 2cf3bfe..901ee35 100644 --- a/test/src/test_select.rs +++ b/test/src/test_select.rs @@ -90,3 +90,28 @@ fn test_select_metadata_two_not() { let r = might_be_async::select! { ! => { 70 } else ! => { 80 } }; assert_eq!(r, 80); } + +#[might_be_async::func] +fn pick(toggle: bool) -> &'static str { + might_be_async::select! { "metadata_async" => { + if toggle { "async_on_a" } else { "async_on_b" } + } else ! => { + if toggle { "sync_a" } else { "sync_b" } + }} +} + +#[cfg(not(feature = "metadata_async"))] +#[test] +fn test_select_inside_func_sync() { + assert_eq!(pick(true), "sync_a"); + assert_eq!(pick(false), "sync_b"); +} + +#[cfg(feature = "metadata_async")] +#[test] +fn test_select_inside_func_async() { + let r = futures::executor::block_on(async { pick(true).await }); + assert_eq!(r, "async_on_a"); + let r = futures::executor::block_on(async { pick(false).await }); + assert_eq!(r, "async_on_b"); +} -- cgit