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#[inline]
92#[must_use]
93pub fn new_split_cache(s: &str, delimiter: char) -> core::str::Split<'_, char> {
94 s.split(delimiter)
95}
96
97#[must_use]
111pub fn join_optimized(parts: &[&str], delimiter: &str) -> String {
112 if parts.is_empty() {
113 return String::new();
114 }
115
116 if parts.len() == 1 {
117 return parts[0].to_string();
118 }
119
120 let total_content_len: usize = parts.iter().map(|s| s.len()).sum();
122 let delimiter_len = delimiter.len() * (parts.len().saturating_sub(1));
123 let total_capacity = total_content_len + delimiter_len;
124
125 let mut result = String::with_capacity(total_capacity);
126
127 for (i, part) in parts.iter().enumerate() {
128 if i > 0 {
129 result.push_str(delimiter);
130 }
131 result.push_str(part);
132 }
133
134 result
135}
136
137#[cfg(test)]
152#[must_use]
153pub fn count_char(s: &str, target: char) -> usize {
154 if target.is_ascii() {
155 let byte = target as u8;
156 #[allow(clippy::naive_bytecount)]
157 s.as_bytes().iter().filter(|&&b| b == byte).count()
158 } else {
159 s.chars().filter(|&c| c == target).count()
160 }
161}
162
163#[cfg(test)]
178#[must_use]
179pub fn find_nth_char(s: &str, target: char, n: usize) -> Option<usize> {
180 let mut count = 0;
181 for (pos, c) in s.char_indices() {
182 if c == target {
183 if count == n {
184 return Some(pos);
185 }
186 count += 1;
187 }
188 }
189 None
190}
191
192#[must_use]
210pub fn normalize_string(s: &str, to_lowercase: bool) -> Cow<'_, str> {
211 let trimmed = s.trim();
212 let needs_trim = trimmed.len() != s.len();
213 let needs_lowercase = to_lowercase && trimmed.chars().any(|c| c.is_ascii_uppercase());
214
215 match (needs_trim, needs_lowercase) {
216 (false, false) => Cow::Borrowed(s),
217 (true, false) => Cow::Borrowed(trimmed),
218 (_, true) => Cow::Owned(trimmed.to_ascii_lowercase()),
219 }
220}
221
222pub fn replace_chars<F>(s: &str, replacer: F) -> Cow<'_, str>
237where
238 F: Fn(char) -> Option<char>,
239{
240 let mut chars = s.char_indices();
242 while let Some((i, c)) = chars.next() {
243 if let Some(replacement) = replacer(c) {
244 let mut result = String::with_capacity(s.len());
246 result.push_str(&s[..i]);
247 result.push(replacement);
248 for (_, c) in chars {
249 if let Some(r) = replacer(c) {
250 result.push(r);
251 } else {
252 result.push(c);
253 }
254 }
255 return Cow::Owned(result);
256 }
257 }
258 Cow::Borrowed(s)
259}
260
261#[allow(clippy::cast_possible_truncation)]
270pub mod char_validation {
271 const ASCII_ALPHANUMERIC: [bool; 128] = {
273 let mut table = [false; 128];
274 let mut i = 0;
275 while i < 128 {
276 table[i] = matches!(i as u8, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z');
277 i += 1;
278 }
279 table
280 };
281
282 const KEY_CHARS: [bool; 128] = {
283 let mut table = [false; 128];
284 let mut i = 0;
285 while i < 128 {
286 table[i] =
287 matches!(i as u8, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'-' | b'.');
288 i += 1;
289 }
290 table
291 };
292
293 #[inline]
295 #[must_use]
296 pub fn is_ascii_alphanumeric_fast(c: char) -> bool {
297 if c.is_ascii() {
298 ASCII_ALPHANUMERIC[c as u8 as usize]
299 } else {
300 false
301 }
302 }
303
304 #[inline]
306 #[must_use]
307 pub fn is_key_char_fast(c: char) -> bool {
308 if c.is_ascii() {
309 KEY_CHARS[c as u8 as usize]
310 } else {
311 false
312 }
313 }
314
315 #[inline]
317 #[must_use]
318 pub fn is_separator(c: char) -> bool {
319 matches!(c, '_' | '-' | '.' | '/' | ':' | '|')
320 }
321
322 #[inline]
324 #[must_use]
325 pub fn is_whitespace_fast(c: char) -> bool {
326 matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0B' | '\x0C')
327 }
328}
329
330#[must_use]
349pub const fn hash_algorithm() -> &'static str {
350 #[cfg(feature = "fast")]
351 {
352 #[cfg(any(
353 all(target_arch = "x86_64", target_feature = "aes"),
354 all(target_arch = "aarch64", target_feature = "aes")
355 ))]
356 return "GxHash";
357
358 #[cfg(not(any(
359 all(target_arch = "x86_64", target_feature = "aes"),
360 all(target_arch = "aarch64", target_feature = "aes")
361 )))]
362 return "AHash (GxHash fallback)";
363 }
364
365 #[cfg(all(feature = "secure", not(feature = "fast")))]
366 return "AHash";
367
368 #[cfg(all(feature = "crypto", not(any(feature = "fast", feature = "secure"))))]
369 return "Blake3";
370
371 #[cfg(not(any(feature = "fast", feature = "secure", feature = "crypto")))]
372 {
373 #[cfg(feature = "std")]
374 return "DefaultHasher";
375
376 #[cfg(not(feature = "std"))]
377 return "FNV-1a";
378 }
379}
380
381#[cfg(test)]
386mod tests {
387 use super::*;
388
389 use crate::ValidationResult;
390 #[cfg(not(feature = "std"))]
391 use alloc::vec;
392 #[cfg(not(feature = "std"))]
393 use alloc::vec::Vec;
394
395 #[test]
396 fn test_add_prefix_suffix() {
397 let result = add_prefix_optimized("test", "prefix_");
398 assert_eq!(result, "prefix_test");
399
400 let result = add_suffix_optimized("test", "_suffix");
401 assert_eq!(result, "test_suffix");
402 }
403
404 #[test]
405 fn test_join_optimized() {
406 let parts = vec!["a", "b", "c"];
407 let result = join_optimized(&parts, "_");
408 assert_eq!(result, "a_b_c");
409
410 let empty: Vec<&str> = vec![];
411 let result = join_optimized(&empty, "_");
412 assert_eq!(result, "");
413
414 let single = vec!["alone"];
415 let result = join_optimized(&single, "_");
416 assert_eq!(result, "alone");
417 }
418
419 #[test]
420 fn test_char_validation() {
421 use char_validation::*;
422
423 assert!(is_ascii_alphanumeric_fast('a'));
424 assert!(is_ascii_alphanumeric_fast('Z'));
425 assert!(is_ascii_alphanumeric_fast('5'));
426 assert!(!is_ascii_alphanumeric_fast('_'));
427 assert!(!is_ascii_alphanumeric_fast('ñ'));
428
429 assert!(is_key_char_fast('a'));
430 assert!(is_key_char_fast('_'));
431 assert!(is_key_char_fast('-'));
432 assert!(is_key_char_fast('.'));
433 assert!(!is_key_char_fast(' '));
434
435 assert!(is_separator('_'));
436 assert!(is_separator('/'));
437 assert!(!is_separator('a'));
438
439 assert!(is_whitespace_fast(' '));
440 assert!(is_whitespace_fast('\t'));
441 assert!(!is_whitespace_fast('a'));
442 }
443
444 #[test]
445 fn test_string_utilities() {
446 assert_eq!(count_char("hello_world_test", '_'), 2);
447 assert_eq!(count_char("no_underscores", '_'), 1);
448
449 assert_eq!(find_nth_char("a_b_c_d", '_', 0), Some(1));
450 assert_eq!(find_nth_char("a_b_c_d", '_', 1), Some(3));
451 assert_eq!(find_nth_char("a_b_c_d", '_', 2), Some(5));
452 assert_eq!(find_nth_char("a_b_c_d", '_', 3), None);
453 }
454
455 #[test]
456 fn test_normalize_string() {
457 let result = normalize_string(" Hello ", true);
458 assert_eq!(result, "hello");
459
460 let result = normalize_string("hello", true);
461 assert_eq!(result, "hello");
462
463 let result = normalize_string(" hello ", false);
464 assert_eq!(result, "hello");
465
466 let result = normalize_string("hello", false);
467 assert!(matches!(result, Cow::Borrowed("hello")));
468 }
469
470 #[test]
471 fn test_float_comparison() {
472 const EPSILON: f64 = 1e-10;
473 let result = ValidationResult {
474 total_processed: 2,
475 valid: vec!["key1".to_string(), "key2".to_string()],
476 errors: vec![],
477 };
478
479 assert!((result.success_rate() - 100.0).abs() < EPSILON);
482 }
483
484 #[test]
485 fn test_replace_chars() {
486 let result = replace_chars("hello-world", |c| if c == '-' { Some('_') } else { None });
487 assert_eq!(result, "hello_world");
488
489 let result = replace_chars("hello_world", |c| if c == '-' { Some('_') } else { None });
490 assert!(matches!(result, Cow::Borrowed("hello_world")));
491 }
492
493 #[test]
494 fn test_replace_chars_fixed() {
495 let result = replace_chars("hello-world", |c| if c == '-' { Some('_') } else { None });
496 assert_eq!(result, "hello_world");
497
498 let result = replace_chars("hello_world", |c| if c == '-' { Some('_') } else { None });
499 assert!(matches!(result, Cow::Borrowed("hello_world")));
500
501 let result = replace_chars("a-b-c", |c| if c == '-' { Some('_') } else { None });
503 assert_eq!(result, "a_b_c");
504
505 let result = replace_chars("hello", |c| if c == 'x' { Some('y') } else { None });
507 assert!(matches!(result, Cow::Borrowed(_)));
508
509 let result = replace_chars("", |c| if c == 'x' { Some('y') } else { None });
511 assert_eq!(result, "");
512 }
513}