diff --git a/src/string.rs b/src/string.rs index 1ad7465..dd2cc52 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,7 +1,7 @@ use alloc::{ borrow::{Cow, ToOwned}, boxed::Box, - string::{String}, + string::String, sync::Arc, }; use core::{borrow::Borrow, hash::Hash, str::FromStr}; @@ -35,7 +35,7 @@ fn truncate_str(string: &str, max_len: usize) -> &str { } } - unreachable!("Len 0 is a char boundary") + unreachable!("Len 0 is a char boundary"); } /// A fixed size String with length provided at creation denoted in [`ValidLength`], by default [`u32`]. @@ -61,10 +61,13 @@ impl FixedString { /// This method will not allocate, or copy the string data. /// /// See [`Self::from_string_trunc`] for truncation behaviour. - pub fn from_static_trunc(val: &'static str) -> Self { - Self(FixedStringRepr::Static(StaticStr::from_static_str( - truncate_str(val, LenT::MAX.to_usize()), - ))) + pub fn from_static_trunc(mut val: &'static str) -> Self { + let max_len = LenT::MAX.to_usize(); + if val.len() > max_len { + val = truncate_str(val, max_len); + } + + Self(FixedStringRepr::Static(StaticStr::from_static_str(val))) } /// Converts a `&str` into a [`FixedString`], allocating if the value cannot fit "inline". @@ -93,7 +96,7 @@ impl FixedString { /// For lossless fallible conversion, convert to [`Box`] using [`String::into_boxed_str`] and use [`TryFrom`]. #[must_use] pub fn from_string_trunc(str: String) -> Self { - match str.into_boxed_str().try_into() { + match str.try_into() { Ok(val) => val, Err(err) => Self::from_string_trunc(truncate_string(err, LenT::MAX.to_usize())), } @@ -438,6 +441,29 @@ mod test { } } + // primarily intended to ensure no hangs occur + #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] + fn check_u32_partial_roundtrip_generic(to_fixed: fn(String) -> FixedString) { + for i in 0..=400u32 { + let original = "a".repeat(i as usize); + let fixed = to_fixed(original); + + assert!(fixed.bytes().all(|c| c == b'a')); + assert_eq!(fixed.len(), i); + + if !fixed.is_static() { + assert_eq!(fixed.is_inline(), fixed.len() <= 12); + } + } + } + + fn check_default_generic() { + let fixed = FixedString::::default(); + + assert!(fixed.is_static()); + assert_eq!(fixed.as_str(), ""); + } + #[test] fn test_truncating_behaviour() { const STR: &str = "______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________🦀"; @@ -482,6 +508,23 @@ mod test { }); } + #[test] + #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] + fn check_u32_partial_roundtrip() { + check_u32_partial_roundtrip_generic(|original| { + FixedString::::try_from(original).unwrap() + }); + } + + #[test] + #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] + fn check_u32_partial_roundtrip_static() { + check_u32_partial_roundtrip_generic(|original| { + let static_str = Box::leak(original.into_boxed_str()); + FixedString::from_static_trunc(static_str) + }); + } + #[test] #[cfg(feature = "serde")] fn check_u8_roundtrip_serde() { @@ -504,6 +547,22 @@ mod test { }); } + #[test] + fn check_default_u8() { + check_default_generic::(); + } + + #[test] + fn check_default_u16() { + check_default_generic::(); + } + + #[test] + #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] + fn check_default_u32() { + check_default_generic::(); + } + #[test] fn check_sizes() { type DoubleOpt = Option>;