1use smartstring::alias::String as SmartString;
7
8#[cfg(not(feature = "std"))]
9use alloc::borrow::Cow;
10#[cfg(not(feature = "std"))]
11use alloc::string::{String, ToString};
12#[cfg(feature = "std")]
13use std::borrow::Cow;
14
15#[must_use]
33pub fn add_prefix_optimized(key: &str, prefix: &str) -> SmartString {
34 let total = prefix.len() + key.len();
35 if total <= 23 {
36 let mut result = SmartString::new();
38 result.push_str(prefix);
39 result.push_str(key);
40 result
41 } else {
42 let mut s = String::with_capacity(total);
43 s.push_str(prefix);
44 s.push_str(key);
45 SmartString::from(s)
46 }
47}
48
49#[must_use]
63pub fn add_suffix_optimized(key: &str, suffix: &str) -> SmartString {
64 let total = key.len() + suffix.len();
65 if total <= 23 {
66 let mut result = SmartString::new();
67 result.push_str(key);
68 result.push_str(suffix);
69 result
70 } else {
71 let mut s = String::with_capacity(total);
72 s.push_str(key);
73 s.push_str(suffix);
74 SmartString::from(s)
75 }
76}
77
78#[must_use]
92pub fn new_split_cache(s: &str, delimiter: char) -> core::str::Split<'_, char> {
93 s.split(delimiter)
94}
95
96#[must_use]
110pub fn join_optimized(parts: &[&str], delimiter: &str) -> String {
111 if parts.is_empty() {
112 return String::new();
113 }
114
115 if parts.len() == 1 {
116 return parts[0].to_string();
117 }
118
119 let total_content_len: usize = parts.iter().map(|s| s.len()).sum();
121 let delimiter_len = delimiter.len() * (parts.len().saturating_sub(1));
122 let total_capacity = total_content_len + delimiter_len;
123
124 let mut result = String::with_capacity(total_capacity);
125
126 for (i, part) in parts.iter().enumerate() {
127 if i > 0 {
128 result.push_str(delimiter);
129 }
130 result.push_str(part);
131 }
132
133 result
134}
135
136#[inline]
148#[must_use]
149pub fn is_ascii_only(s: &str) -> bool {
150 s.is_ascii()
151}
152
153#[must_use]
168pub fn count_char(s: &str, target: char) -> usize {
169 if target.is_ascii() {
170 let byte = target as u8;
171 #[allow(clippy::naive_bytecount)]
172 s.as_bytes().iter().filter(|&&b| b == byte).count()
173 } else {
174 s.chars().filter(|&c| c == target).count()
175 }
176}
177
178#[must_use]
193pub fn find_nth_char(s: &str, target: char, n: usize) -> Option<usize> {
194 let mut count = 0;
195 for (pos, c) in s.char_indices() {
196 if c == target {
197 if count == n {
198 return Some(pos);
199 }
200 count += 1;
201 }
202 }
203 None
204}
205
206#[must_use]
224pub fn normalize_string(s: &str, to_lowercase: bool) -> Cow<'_, str> {
225 let trimmed = s.trim();
226 let needs_trim = trimmed.len() != s.len();
227 let needs_lowercase = to_lowercase && trimmed.chars().any(|c| c.is_ascii_uppercase());
228
229 match (needs_trim, needs_lowercase) {
230 (false, false) => Cow::Borrowed(s),
231 (true, false) => Cow::Owned(trimmed.to_string()),
232 (_, true) => Cow::Owned(trimmed.to_ascii_lowercase()),
233 }
234}
235
236pub fn replace_chars<F>(s: &str, replacer: F) -> Cow<'_, str>
251where
252 F: Fn(char) -> Option<char>,
253{
254 let mut chars = s.char_indices();
256 while let Some((i, c)) = chars.next() {
257 if let Some(replacement) = replacer(c) {
258 let mut result = String::with_capacity(s.len());
260 result.push_str(&s[..i]);
261 result.push(replacement);
262 for (_, c) in chars {
263 if let Some(r) = replacer(c) {
264 result.push(r);
265 } else {
266 result.push(c);
267 }
268 }
269 return Cow::Owned(result);
270 }
271 }
272 Cow::Borrowed(s)
273}
274
275#[allow(clippy::cast_possible_truncation)]
284pub mod char_validation {
285 const ASCII_ALPHANUMERIC: [bool; 128] = {
287 let mut table = [false; 128];
288 let mut i = 0;
289 while i < 128 {
290 table[i] = matches!(i as u8, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z');
291 i += 1;
292 }
293 table
294 };
295
296 const KEY_CHARS: [bool; 128] = {
297 let mut table = [false; 128];
298 let mut i = 0;
299 while i < 128 {
300 table[i] =
301 matches!(i as u8, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'-' | b'.');
302 i += 1;
303 }
304 table
305 };
306
307 #[inline]
309 #[must_use]
310 pub fn is_ascii_alphanumeric_fast(c: char) -> bool {
311 if c.is_ascii() {
312 ASCII_ALPHANUMERIC[c as u8 as usize]
313 } else {
314 false
315 }
316 }
317
318 #[inline]
320 #[must_use]
321 pub fn is_key_char_fast(c: char) -> bool {
322 if c.is_ascii() {
323 KEY_CHARS[c as u8 as usize]
324 } else {
325 false
326 }
327 }
328
329 #[inline]
331 #[must_use]
332 pub fn is_separator(c: char) -> bool {
333 matches!(c, '_' | '-' | '.' | '/' | ':' | '|')
334 }
335
336 #[inline]
338 #[must_use]
339 pub fn is_whitespace_fast(c: char) -> bool {
340 matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0B' | '\x0C')
341 }
342}
343
344#[must_use]
361pub fn string_memory_usage(s: &str) -> usize {
362 core::mem::size_of::<String>() + s.len()
364}
365
366#[must_use]
379pub fn smart_string_memory_usage(s: &str) -> usize {
380 if s.len() <= 23 {
382 core::mem::size_of::<SmartString>()
383 } else {
384 core::mem::size_of::<SmartString>() + s.len()
385 }
386}
387
388#[must_use]
407pub const fn hash_algorithm() -> &'static str {
408 #[cfg(feature = "fast")]
409 {
410 #[cfg(any(
411 all(target_arch = "x86_64", target_feature = "aes"),
412 all(target_arch = "aarch64", target_feature = "aes")
413 ))]
414 return "GxHash";
415
416 #[cfg(not(any(
417 all(target_arch = "x86_64", target_feature = "aes"),
418 all(target_arch = "aarch64", target_feature = "aes")
419 )))]
420 return "AHash (GxHash fallback)";
421 }
422
423 #[cfg(all(feature = "secure", not(feature = "fast")))]
424 return "AHash";
425
426 #[cfg(all(feature = "crypto", not(any(feature = "fast", feature = "secure"))))]
427 return "Blake3";
428
429 #[cfg(not(any(feature = "fast", feature = "secure", feature = "crypto")))]
430 {
431 #[cfg(feature = "std")]
432 return "DefaultHasher";
433
434 #[cfg(not(feature = "std"))]
435 return "FNV-1a";
436 }
437}
438
439#[cfg(test)]
444mod tests {
445 use super::*;
446
447 use crate::ValidationResult;
448 #[cfg(not(feature = "std"))]
449 use alloc::vec;
450 #[cfg(not(feature = "std"))]
451 use alloc::vec::Vec;
452
453 #[test]
454 fn test_add_prefix_suffix() {
455 let result = add_prefix_optimized("test", "prefix_");
456 assert_eq!(result, "prefix_test");
457
458 let result = add_suffix_optimized("test", "_suffix");
459 assert_eq!(result, "test_suffix");
460 }
461
462 #[test]
463 fn test_join_optimized() {
464 let parts = vec!["a", "b", "c"];
465 let result = join_optimized(&parts, "_");
466 assert_eq!(result, "a_b_c");
467
468 let empty: Vec<&str> = vec![];
469 let result = join_optimized(&empty, "_");
470 assert_eq!(result, "");
471
472 let single = vec!["alone"];
473 let result = join_optimized(&single, "_");
474 assert_eq!(result, "alone");
475 }
476
477 #[test]
478 fn test_char_validation() {
479 use char_validation::*;
480
481 assert!(is_ascii_alphanumeric_fast('a'));
482 assert!(is_ascii_alphanumeric_fast('Z'));
483 assert!(is_ascii_alphanumeric_fast('5'));
484 assert!(!is_ascii_alphanumeric_fast('_'));
485 assert!(!is_ascii_alphanumeric_fast('ñ'));
486
487 assert!(is_key_char_fast('a'));
488 assert!(is_key_char_fast('_'));
489 assert!(is_key_char_fast('-'));
490 assert!(is_key_char_fast('.'));
491 assert!(!is_key_char_fast(' '));
492
493 assert!(is_separator('_'));
494 assert!(is_separator('/'));
495 assert!(!is_separator('a'));
496
497 assert!(is_whitespace_fast(' '));
498 assert!(is_whitespace_fast('\t'));
499 assert!(!is_whitespace_fast('a'));
500 }
501
502 #[test]
503 fn test_string_utilities() {
504 assert!(is_ascii_only("hello"));
505 assert!(!is_ascii_only("héllo"));
506
507 assert_eq!(count_char("hello_world_test", '_'), 2);
508 assert_eq!(count_char("no_underscores", '_'), 1);
509
510 assert_eq!(find_nth_char("a_b_c_d", '_', 0), Some(1));
511 assert_eq!(find_nth_char("a_b_c_d", '_', 1), Some(3));
512 assert_eq!(find_nth_char("a_b_c_d", '_', 2), Some(5));
513 assert_eq!(find_nth_char("a_b_c_d", '_', 3), None);
514 }
515
516 #[test]
517 fn test_normalize_string() {
518 let result = normalize_string(" Hello ", true);
519 assert_eq!(result, "hello");
520
521 let result = normalize_string("hello", true);
522 assert_eq!(result, "hello");
523
524 let result = normalize_string(" hello ", false);
525 assert_eq!(result, "hello");
526
527 let result = normalize_string("hello", false);
528 assert!(matches!(result, Cow::Borrowed("hello")));
529 }
530
531 #[test]
532 fn test_memory_utilities() {
533 let s = "hello";
534 let usage = string_memory_usage(s);
535 assert!(usage >= s.len());
536 }
537
538 #[test]
539 fn test_float_comparison() {
540 const EPSILON: f64 = 1e-10;
541 let result = ValidationResult {
542 total_processed: 2,
543 valid: vec!["key1".to_string(), "key2".to_string()],
544 errors: vec![],
545 };
546
547 assert!((result.success_rate() - 100.0).abs() < EPSILON);
550 }
551
552 #[test]
553 fn test_replace_chars() {
554 let result = replace_chars("hello-world", |c| if c == '-' { Some('_') } else { None });
555 assert_eq!(result, "hello_world");
556
557 let result = replace_chars("hello_world", |c| if c == '-' { Some('_') } else { None });
558 assert!(matches!(result, Cow::Borrowed("hello_world")));
559 }
560
561 #[test]
562 fn test_replace_chars_fixed() {
563 let result = replace_chars("hello-world", |c| if c == '-' { Some('_') } else { None });
564 assert_eq!(result, "hello_world");
565
566 let result = replace_chars("hello_world", |c| if c == '-' { Some('_') } else { None });
567 assert!(matches!(result, Cow::Borrowed("hello_world")));
568
569 let result = replace_chars("a-b-c", |c| if c == '-' { Some('_') } else { None });
571 assert_eq!(result, "a_b_c");
572
573 let result = replace_chars("hello", |c| if c == 'x' { Some('y') } else { None });
575 assert!(matches!(result, Cow::Borrowed(_)));
576
577 let result = replace_chars("", |c| if c == 'x' { Some('y') } else { None });
579 assert_eq!(result, "");
580 }
581}