diff --git a/CHANGELOG.md b/CHANGELOG.md index aabd7ea..0025d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.6](https://github.com/MidasLamb/non-empty-string/compare/v0.2.5...v0.2.6) - 2025-04-09 + +### Added + +- `non_empty_string!()` macro to create a `NonEmptyString` at compile time , thanks @patskovn in #19 (requires `macros` feature flag) + ## [0.2.5](https://github.com/MidasLamb/non-empty-string/compare/v0.2.4...v0.2.5) - 2024-10-15 ### Added diff --git a/Cargo.toml b/Cargo.toml index 0fd59c6..d1551ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1", features = ["derive"] } [features] default = [] +macros = [] serde = ["dep:serde"] diff --git a/src/lib.rs b/src/lib.rs index f225c34..8477b8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,9 @@ use delegate::delegate; #[cfg(feature = "serde")] mod serde_support; +#[cfg(feature = "macros")] +mod macros; + mod trait_impls; /// A simple String wrapper type, similar to NonZeroUsize and friends. diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..1b7adda --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,57 @@ +#[macro_export] +/// Creates a `NonEmptyString` from a string literal at compile time. +/// +/// This macro ensures that the provided string is **not empty** at compile time, +/// preventing runtime errors due to empty strings. +/// +/// # Examples +/// +/// ``` +/// use non_empty_string::{non_empty_string, NonEmptyString}; +/// +/// let s: NonEmptyString = non_empty_string!("Hello, Rust!"); +/// assert_eq!(s, NonEmptyString::new("Hello, Rust!".to_string()).unwrap()); +/// ``` +/// +/// # Compile-time Failure +/// +/// If an empty string is provided, this macro will cause a **compile-time error**. +/// +/// ```compile_fail +/// use non_empty_string::non_empty_string; +/// +/// let s = non_empty_string!(""); +/// ``` +macro_rules! non_empty_string { + ($s:expr) => {{ + // Compile-time assertion to ensure the string is non-empty + const _: () = assert!(!$s.is_empty(), "String cannot be empty"); + + // Create a NonEmptyString, unsafely wrapping since we've checked it's valid + unsafe { $crate::NonEmptyString::new_unchecked($s.to_string()) } + }}; +} + +#[cfg(test)] +mod tests { + // We explicitely DO NOT do `use crate::NonEmptyString` or anything of the sorts to ensure the macro has proper hygiene. + // Otherwise tests might pass, but if a user does `non_empty_string::non_empty_string!("A")`, they might get compilation + // errors that `NonEmptyString` is not in scope. + + const NON_EMPTY_STRING: &'static str = "non-empty-string"; + + #[test] + fn test_const_non_empty_string_macro_valid() { + let s = non_empty_string!(NON_EMPTY_STRING); + assert_eq!( + s, + crate::NonEmptyString::try_from(NON_EMPTY_STRING).unwrap() + ); + } + + #[test] + fn test_inline_non_empty_string_macro_valid() { + let s = non_empty_string!("Test String"); + assert_eq!(s, crate::NonEmptyString::try_from("Test String").unwrap()); + } +}