domain_key/key.rs
1//! Core Key implementation for domain-key
2//!
3//! This module contains the main `Key<T>` structure and its implementation,
4//! providing high-performance, type-safe key handling with extensive optimizations.
5
6use core::fmt;
7use core::hash::{Hash, Hasher};
8use core::marker::PhantomData;
9use core::str::FromStr;
10
11#[cfg(not(feature = "std"))]
12use alloc::borrow::Cow;
13#[cfg(not(feature = "std"))]
14use alloc::string::{String, ToString};
15
16#[cfg(feature = "std")]
17use std::borrow::Cow;
18
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22use smartstring::alias::String as SmartString;
23
24use crate::domain::KeyDomain;
25use crate::error::KeyParseError;
26use crate::utils;
27
28// ============================================================================
29// CONSTANTS
30// ============================================================================
31
32/// Default maximum allowed length for any key
33///
34/// This is a reasonable default that balances usability with performance.
35/// Keys up to this length can benefit from stack allocation optimizations.
36/// Domains can override this with their own limits.
37pub const DEFAULT_MAX_KEY_LENGTH: usize = 64;
38
39// ============================================================================
40// SPLIT ITERATOR TYPES
41// ============================================================================
42
43/// Split cache type for consistent API
44pub type SplitCache<'a> = core::str::Split<'a, char>;
45
46/// Split iterator with consistent API
47#[derive(Debug)]
48pub struct SplitIterator<'a>(SplitCache<'a>);
49
50impl<'a> Iterator for SplitIterator<'a> {
51 type Item = &'a str;
52
53 #[inline]
54 fn next(&mut self) -> Option<Self::Item> {
55 self.0.next()
56 }
57}
58
59// ============================================================================
60// CORE KEY IMPLEMENTATION
61// ============================================================================
62
63/// High-performance generic key type with advanced optimizations
64///
65/// This is the core key type that provides type safety through the domain
66/// marker `T`. Keys are immutable after creation and use `SmartString` for
67/// optimal memory usage (stack allocation for short keys, heap for longer ones).
68///
69/// # Performance Characteristics
70///
71/// - **Memory Layout**: 32 bytes total (fits in single cache line)
72/// - **Hash Access**: O(1) via pre-computed hash
73/// - **Length Access**: O(1) via cached length field
74/// - **String Access**: Direct reference to internal storage
75/// - **Clone**: Efficient via `SmartString`'s copy-on-write semantics
76///
77/// # Type Parameters
78///
79/// * `T` - A domain marker type that implements `KeyDomain`
80///
81/// # Memory Layout
82///
83/// ```text
84/// Key<T> struct (32 bytes, cache-line friendly):
85/// ┌─────────────────────┬──────────┬─────────┬─────────────┐
86/// │ SmartString (24B) │ hash (8B)│ len (4B)│ marker (0B) │
87/// └─────────────────────┴──────────┴─────────┴─────────────┘
88/// ```
89///
90/// Keys use `SmartString` which stores strings up to 23 bytes inline on the stack,
91/// only allocating on the heap for longer strings. Additionally, the pre-computed
92/// hash is stored for O(1) hash operations.
93///
94/// # Examples
95///
96/// ```rust
97/// use domain_key::{Key, Domain, KeyDomain};
98///
99/// #[derive(Debug)]
100/// struct UserDomain;
101///
102/// impl Domain for UserDomain {
103/// const DOMAIN_NAME: &'static str = "user";
104/// }
105///
106/// impl KeyDomain for UserDomain {
107/// const MAX_LENGTH: usize = 32;
108/// }
109///
110/// type UserKey = Key<UserDomain>;
111///
112/// let key = UserKey::new("john_doe")?;
113/// assert_eq!(key.as_str(), "john_doe");
114/// assert_eq!(key.domain(), "user");
115/// assert_eq!(key.len(), 8);
116/// # Ok::<(), domain_key::KeyParseError>(())
117/// ```
118#[derive(Debug)]
119pub struct Key<T: KeyDomain> {
120 /// Internal string storage using `SmartString` for optimal memory usage
121 inner: SmartString,
122
123 /// Pre-computed hash value for O(1) hash operations
124 ///
125 /// This hash is computed once during key creation and cached for the
126 /// lifetime of the key, providing significant performance benefits
127 /// for hash-based collections.
128 hash: u64,
129
130 /// Cached length for O(1) length access
131 ///
132 /// This optimization eliminates the need to traverse the string to
133 /// determine its length, which can be a significant performance
134 /// improvement in hot paths.
135 length: u32,
136
137 /// Zero-sized type marker for compile-time type safety
138 ///
139 /// This field provides compile-time type safety without any runtime
140 /// overhead. Different domain types cannot be mixed or compared.
141 _marker: PhantomData<T>,
142}
143
144// Manual Clone implementation to ensure optimal performance
145impl<T: KeyDomain> Clone for Key<T> {
146 /// Efficient clone implementation
147 ///
148 /// Cloning a key is efficient due to `SmartString`'s optimizations:
149 /// - For inline strings (≤23 chars): Simple memory copy
150 /// - For heap strings: Reference counting or copy-on-write
151 #[inline]
152 fn clone(&self) -> Self {
153 Self {
154 inner: self.inner.clone(),
155 hash: self.hash,
156 length: self.length,
157 _marker: PhantomData,
158 }
159 }
160}
161
162// Manual PartialEq/Eq — compare only the key string, not cached fields
163impl<T: KeyDomain> PartialEq for Key<T> {
164 #[inline]
165 fn eq(&self, other: &Self) -> bool {
166 self.inner == other.inner
167 }
168}
169
170impl<T: KeyDomain> Eq for Key<T> {}
171
172// Manual PartialOrd/Ord — compare only the key string
173impl<T: KeyDomain> PartialOrd for Key<T> {
174 #[inline]
175 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
176 Some(self.cmp(other))
177 }
178}
179
180impl<T: KeyDomain> Ord for Key<T> {
181 #[inline]
182 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
183 self.inner.cmp(&other.inner)
184 }
185}
186
187// Manual Hash implementation using cached hash for maximum performance
188impl<T: KeyDomain> Hash for Key<T> {
189 /// O(1) hash implementation using pre-computed hash
190 ///
191 /// This is significantly faster than re-hashing the string content
192 /// every time the key is used in hash-based collections.
193 #[inline]
194 fn hash<H: Hasher>(&self, state: &mut H) {
195 state.write_u64(self.hash);
196 }
197}
198
199// Conditional Serde support for serialization/deserialization
200#[cfg(feature = "serde")]
201impl<T: KeyDomain> Serialize for Key<T> {
202 /// Serialize the key as its string representation
203 ///
204 /// Keys are serialized as their string content, not including
205 /// the cached hash or length for efficiency and compatibility.
206 #[inline]
207 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
208 where
209 S: serde::Serializer,
210 {
211 self.inner.serialize(serializer)
212 }
213}
214
215#[cfg(feature = "serde")]
216impl<'de, T: KeyDomain> Deserialize<'de> for Key<T> {
217 /// Deserialize and validate a key from its string representation
218 ///
219 /// This implementation chooses the optimal deserialization strategy
220 /// based on the format (human-readable vs binary) for best performance.
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: serde::Deserializer<'de>,
224 {
225 if deserializer.is_human_readable() {
226 // For human-readable formats (JSON, YAML), use zero-copy when possible
227 let s = <&str>::deserialize(deserializer)?;
228 Key::new(s).map_err(serde::de::Error::custom)
229 } else {
230 // For binary formats, deserialize as owned string
231 let s = String::deserialize(deserializer)?;
232 Key::from_string(s).map_err(serde::de::Error::custom)
233 }
234 }
235}
236
237// ============================================================================
238// KEY IMPLEMENTATION - CORE METHODS
239// ============================================================================
240
241impl<T: KeyDomain> Key<T> {
242 /// Creates a new key with comprehensive validation and optimization
243 ///
244 /// This method performs both common validation (length, characters) and
245 /// domain-specific validation according to the key's domain type. It
246 /// automatically chooses the optimal creation path based on the input
247 /// characteristics and domain configuration.
248 ///
249 /// # Arguments
250 ///
251 /// * `key` - String-like input that will be normalized and validated
252 ///
253 /// # Returns
254 ///
255 /// * `Ok(Key<T>)` if the key is valid
256 /// * `Err(KeyParseError)` with the specific validation failure
257 ///
258 /// # Errors
259 ///
260 /// Returns `KeyParseError` if the key fails common validation (empty, too
261 /// long, invalid characters) or domain-specific validation rules
262 ///
263 /// # Examples
264 ///
265 /// ```rust
266 /// use domain_key::{Key, Domain, KeyDomain};
267 ///
268 /// #[derive(Debug)]
269 /// struct TestDomain;
270 /// impl Domain for TestDomain {
271 /// const DOMAIN_NAME: &'static str = "test";
272 /// }
273 /// impl KeyDomain for TestDomain {}
274 /// type TestKey = Key<TestDomain>;
275 ///
276 /// let key = TestKey::new("valid_key")?;
277 /// assert_eq!(key.as_str(), "valid_key");
278 ///
279 /// // Invalid keys return descriptive errors
280 /// let error = TestKey::new("").unwrap_err();
281 /// assert!(matches!(error, domain_key::KeyParseError::Empty));
282 /// # Ok::<(), domain_key::KeyParseError>(())
283 /// ```
284 #[inline]
285 pub fn new(key: impl AsRef<str>) -> Result<Self, KeyParseError> {
286 let key_str = key.as_ref();
287 Self::new_optimized(key_str)
288 }
289
290 /// Optimized implementation for key creation
291 ///
292 /// This method uses performance optimizations when available:
293 /// - Stack allocation for short keys
294 /// - Fast validation paths
295 /// - Cached operations
296 ///
297 /// # Errors
298 ///
299 /// Returns `KeyParseError` if the constructed key fails validation
300 fn new_optimized(key: &str) -> Result<Self, KeyParseError> {
301 // Step 1: Reject obviously empty input before normalization
302 if key.trim().is_empty() {
303 return Err(KeyParseError::Empty);
304 }
305
306 // Step 2: Normalization (trimming, lowercasing, domain-specific)
307 let normalized = Self::normalize(key);
308
309 // Step 3: Common validation on the normalized result
310 Self::validate_common(&normalized)?;
311
312 // Step 4: Domain-specific validation
313 T::validate_domain_rules(&normalized).map_err(Self::fix_domain_error)?;
314
315 // Step 4: Hash computation and storage
316 let hash = Self::compute_hash(&normalized);
317 let length = u32::try_from(normalized.len()).map_err(|_| KeyParseError::TooLong {
318 max_length: u32::MAX as usize,
319 actual_length: normalized.len(),
320 })?;
321
322 Ok(Self {
323 inner: SmartString::from(normalized.as_ref()),
324 hash,
325 length,
326 _marker: PhantomData,
327 })
328 }
329
330 /// Creates a new key from an owned String with optimized handling
331 ///
332 /// This method is more efficient when you already have a `String` as it
333 /// can reuse the allocation when possible.
334 ///
335 /// # Arguments
336 ///
337 /// * `key` - Owned string that will be normalized and validated
338 ///
339 /// # Errors
340 ///
341 /// Returns `KeyParseError` if the key fails validation
342 ///
343 /// # Examples
344 ///
345 /// ```rust
346 /// use domain_key::{Key, Domain, KeyDomain};
347 ///
348 /// #[derive(Debug)]
349 /// struct TestDomain;
350 /// impl Domain for TestDomain {
351 /// const DOMAIN_NAME: &'static str = "test";
352 /// }
353 /// impl KeyDomain for TestDomain {}
354 /// type TestKey = Key<TestDomain>;
355 ///
356 /// let key_string = "test_key".to_string();
357 /// let key = TestKey::from_string(key_string)?;
358 /// assert_eq!(key.as_str(), "test_key");
359 /// # Ok::<(), domain_key::KeyParseError>(())
360 /// ```
361 pub fn from_string(key: String) -> Result<Self, KeyParseError> {
362 // Reject obviously empty input before normalization
363 if key.trim().is_empty() {
364 return Err(KeyParseError::Empty);
365 }
366
367 // Normalize efficiently, reusing allocation when possible
368 let normalized = Self::normalize_owned(key);
369
370 // Validate the normalized result
371 Self::validate_common(&normalized)?;
372
373 // Domain validation
374 T::validate_domain_rules(&normalized).map_err(Self::fix_domain_error)?;
375
376 let hash = Self::compute_hash(&normalized);
377 let length = u32::try_from(normalized.len()).map_err(|_| KeyParseError::TooLong {
378 max_length: u32::MAX as usize,
379 actual_length: normalized.len(),
380 })?;
381
382 Ok(Self {
383 inner: SmartString::from(normalized),
384 hash,
385 length,
386 _marker: PhantomData,
387 })
388 }
389
390 /// Create a key from multiple parts separated by a delimiter
391 ///
392 /// This method efficiently constructs a key from multiple string parts,
393 /// using pre-calculated sizing to minimize allocations.
394 ///
395 /// # Arguments
396 ///
397 /// * `parts` - Array of string parts to join
398 /// * `delimiter` - String to use as separator between parts
399 ///
400 /// # Returns
401 ///
402 /// * `Ok(Key<T>)` if the constructed key is valid
403 /// * `Err(KeyParseError)` if validation fails
404 ///
405 /// # Examples
406 ///
407 /// ```rust
408 /// use domain_key::{Key, Domain, KeyDomain};
409 ///
410 /// #[derive(Debug)]
411 /// struct TestDomain;
412 /// impl Domain for TestDomain {
413 /// const DOMAIN_NAME: &'static str = "test";
414 /// }
415 /// impl KeyDomain for TestDomain {}
416 /// type TestKey = Key<TestDomain>;
417 ///
418 /// let key = TestKey::from_parts(&["user", "123", "profile"], "_")?;
419 /// assert_eq!(key.as_str(), "user_123_profile");
420 /// # Ok::<(), domain_key::KeyParseError>(())
421 /// ```
422 /// # Errors
423 ///
424 /// Returns `KeyParseError` if the constructed key fails validation
425 pub fn from_parts(parts: &[&str], delimiter: &str) -> Result<Self, KeyParseError> {
426 if parts.is_empty() {
427 return Err(KeyParseError::Empty);
428 }
429
430 if parts.iter().any(|part| part.is_empty()) {
431 return Err(KeyParseError::InvalidStructure {
432 reason: "Parts cannot contain empty strings",
433 });
434 }
435
436 let joined = parts.join(delimiter);
437
438 if joined.is_empty() {
439 return Err(KeyParseError::Empty);
440 }
441
442 Self::from_string(joined)
443 }
444
445 /// Try to create a key from multiple parts, returning None on failure
446 ///
447 /// This is a convenience method for when you want to handle validation
448 /// failures by ignoring invalid keys rather than handling errors.
449 ///
450 /// # Examples
451 ///
452 /// ```rust
453 /// use domain_key::{Key, Domain, KeyDomain};
454 ///
455 /// #[derive(Debug)]
456 /// struct TestDomain;
457 /// impl Domain for TestDomain {
458 /// const DOMAIN_NAME: &'static str = "test";
459 /// }
460 /// impl KeyDomain for TestDomain {}
461 /// type TestKey = Key<TestDomain>;
462 ///
463 /// let valid = TestKey::try_from_parts(&["user", "123"], "_").unwrap();
464 /// let invalid = TestKey::try_from_parts(&["", ""], "_"); // Returns None
465 /// assert!(invalid.is_none());
466 /// ```
467 #[must_use]
468 pub fn try_from_parts(parts: &[&str], delimiter: &str) -> Option<Self> {
469 Self::from_parts(parts, delimiter).ok()
470 }
471
472 /// Creates a key from a static string without runtime validation
473 ///
474 /// # Warning
475 ///
476 /// The caller must ensure that the static string follows all validation
477 /// rules for the domain (allowed characters, length limits, normalization,
478 /// domain-specific rules). Invalid keys created this way will violate
479 /// internal invariants and may cause unexpected behavior.
480 ///
481 /// Prefer [`try_from_static`](Self::try_from_static) or the [`static_key!`](macro@crate::static_key) macro
482 /// for safe creation of static keys.
483 ///
484 /// # Panics
485 ///
486 /// Panics in debug builds if the key is empty or exceeds `u32::MAX` length.
487 ///
488 /// # Arguments
489 ///
490 /// * `key` - A static string literal that represents a valid key
491 ///
492 /// # Examples
493 ///
494 /// ```rust
495 /// use domain_key::{Key, Domain, KeyDomain};
496 ///
497 /// #[derive(Debug)]
498 /// struct TestDomain;
499 /// impl Domain for TestDomain {
500 /// const DOMAIN_NAME: &'static str = "test";
501 /// }
502 /// impl KeyDomain for TestDomain {}
503 /// type TestKey = Key<TestDomain>;
504 ///
505 /// let key = TestKey::from_static_unchecked("static_key");
506 /// assert_eq!(key.as_str(), "static_key");
507 /// ```
508 #[must_use]
509 pub fn from_static_unchecked(key: &'static str) -> Self {
510 debug_assert!(
511 !key.is_empty(),
512 "from_static_unchecked: key must not be empty"
513 );
514 debug_assert!(
515 key.len() <= T::MAX_LENGTH,
516 "from_static_unchecked: key length {} exceeds domain max {}",
517 key.len(),
518 T::MAX_LENGTH
519 );
520
521 let hash = Self::compute_hash(key);
522 #[allow(clippy::cast_possible_truncation)]
523 let length = key.len() as u32;
524
525 Self {
526 inner: SmartString::from(key),
527 hash,
528 length,
529 _marker: PhantomData,
530 }
531 }
532
533 /// Creates a key from a static string with validation
534 ///
535 /// This is a safer alternative to `from_static_unchecked` that validates
536 /// the key at runtime. The validation cost is paid once, and subsequent
537 /// uses of the key are as fast as the unchecked version.
538 ///
539 /// # Arguments
540 ///
541 /// * `key` - A static string literal to validate and convert
542 ///
543 /// # Errors
544 ///
545 /// Returns `KeyParseError` if the static key fails validation
546 ///
547 /// # Examples
548 ///
549 /// ```rust
550 /// use domain_key::{Key, Domain, KeyDomain};
551 ///
552 /// #[derive(Debug)]
553 /// struct TestDomain;
554 /// impl Domain for TestDomain {
555 /// const DOMAIN_NAME: &'static str = "test";
556 /// }
557 /// impl KeyDomain for TestDomain {}
558 /// type TestKey = Key<TestDomain>;
559 ///
560 /// let key = TestKey::try_from_static("static_key")?;
561 /// assert_eq!(key.as_str(), "static_key");
562 ///
563 /// let invalid = TestKey::try_from_static("");
564 /// assert!(invalid.is_err());
565 /// # Ok::<(), domain_key::KeyParseError>(())
566 /// ```
567 /// # Errors
568 ///
569 /// Returns `KeyParseError` if the constructed key fails validation
570 pub fn try_from_static(key: &'static str) -> Result<Self, KeyParseError> {
571 // Validate and create via the normal path
572 Self::new(key)
573 }
574
575 /// Try to create a key, returning None on validation failure
576 ///
577 /// This is a convenience method for when you want to handle validation
578 /// failures by ignoring invalid keys rather than handling errors.
579 ///
580 /// # Examples
581 ///
582 /// ```rust
583 /// use domain_key::{Key, Domain, KeyDomain};
584 ///
585 /// #[derive(Debug)]
586 /// struct TestDomain;
587 /// impl Domain for TestDomain {
588 /// const DOMAIN_NAME: &'static str = "test";
589 /// }
590 /// impl KeyDomain for TestDomain {}
591 /// type TestKey = Key<TestDomain>;
592 ///
593 /// let valid = TestKey::try_new("valid_key").unwrap();
594 /// let invalid = TestKey::try_new(""); // Returns None
595 /// assert!(invalid.is_none());
596 /// ```
597 #[inline]
598 pub fn try_new(key: impl AsRef<str>) -> Option<Self> {
599 Self::new(key).ok()
600 }
601}
602
603// ============================================================================
604// KEY IMPLEMENTATION - ACCESSOR METHODS
605// ============================================================================
606
607impl<T: KeyDomain> Key<T> {
608 /// Returns the key as a string slice
609 ///
610 /// This is the primary way to access the string content of a key.
611 /// The returned reference is valid for the lifetime of the key.
612 ///
613 /// # Examples
614 ///
615 /// ```rust
616 /// use domain_key::{Key, Domain, KeyDomain};
617 ///
618 /// #[derive(Debug)]
619 /// struct TestDomain;
620 /// impl Domain for TestDomain {
621 /// const DOMAIN_NAME: &'static str = "test";
622 /// }
623 /// impl KeyDomain for TestDomain {}
624 /// type TestKey = Key<TestDomain>;
625 ///
626 /// let key = TestKey::new("example")?;
627 /// assert_eq!(key.as_str(), "example");
628 /// # Ok::<(), domain_key::KeyParseError>(())
629 /// ```
630 #[inline]
631 #[must_use]
632 pub fn as_str(&self) -> &str {
633 &self.inner
634 }
635
636 /// Returns the domain name for this key type
637 ///
638 /// This is a compile-time constant that identifies which domain
639 /// this key belongs to.
640 ///
641 /// # Examples
642 ///
643 /// ```rust
644 /// use domain_key::{Key, Domain, KeyDomain};
645 ///
646 /// #[derive(Debug)]
647 /// struct UserDomain;
648 /// impl Domain for UserDomain {
649 /// const DOMAIN_NAME: &'static str = "user";
650 /// }
651 /// impl KeyDomain for UserDomain {}
652 /// type UserKey = Key<UserDomain>;
653 ///
654 /// let key = UserKey::new("john")?;
655 /// assert_eq!(key.domain(), "user");
656 /// # Ok::<(), domain_key::KeyParseError>(())
657 /// ```
658 #[inline]
659 #[must_use]
660 pub const fn domain(&self) -> &'static str {
661 T::DOMAIN_NAME
662 }
663
664 /// Returns the length of the key string
665 ///
666 /// This is an O(1) operation using a cached length value.
667 ///
668 /// # Examples
669 ///
670 /// ```rust
671 /// use domain_key::{Key, Domain, KeyDomain};
672 ///
673 /// #[derive(Debug)]
674 /// struct TestDomain;
675 /// impl Domain for TestDomain {
676 /// const DOMAIN_NAME: &'static str = "test";
677 /// }
678 /// impl KeyDomain for TestDomain {}
679 /// type TestKey = Key<TestDomain>;
680 ///
681 /// let key = TestKey::new("example")?;
682 /// assert_eq!(key.len(), 7);
683 /// # Ok::<(), domain_key::KeyParseError>(())
684 /// ```
685 #[inline]
686 #[must_use]
687 pub fn len(&self) -> usize {
688 self.length as usize
689 }
690
691 /// Returns true if the key is empty (this should never happen for valid keys)
692 ///
693 /// Since empty keys are rejected during validation, this method should
694 /// always return `false` for properly constructed keys. It's provided
695 /// for completeness and debugging purposes.
696 ///
697 /// # Examples
698 ///
699 /// ```rust
700 /// use domain_key::{Key, Domain, KeyDomain};
701 ///
702 /// #[derive(Debug)]
703 /// struct TestDomain;
704 /// impl Domain for TestDomain {
705 /// const DOMAIN_NAME: &'static str = "test";
706 /// }
707 /// impl KeyDomain for TestDomain {}
708 /// type TestKey = Key<TestDomain>;
709 ///
710 /// let key = TestKey::new("example")?;
711 /// assert!(!key.is_empty());
712 /// # Ok::<(), domain_key::KeyParseError>(())
713 /// ```
714 #[inline]
715 #[must_use]
716 pub fn is_empty(&self) -> bool {
717 self.length == 0
718 }
719
720 /// Returns the cached hash value
721 ///
722 /// This hash is computed once during key creation and cached for the
723 /// lifetime of the key. It's used internally for hash-based collections
724 /// and can be useful for custom hash-based data structures.
725 ///
726 /// **Note:** The hash algorithm depends on the active feature flags
727 /// (`fast`, `secure`, `crypto`, or the default hasher). Keys created
728 /// with different feature configurations will produce different hash
729 /// values. Do not persist or compare hash values across builds with
730 /// different features.
731 ///
732 /// # Examples
733 ///
734 /// ```rust
735 /// use domain_key::{Key, Domain, KeyDomain};
736 ///
737 /// #[derive(Debug)]
738 /// struct TestDomain;
739 /// impl Domain for TestDomain {
740 /// const DOMAIN_NAME: &'static str = "test";
741 /// }
742 /// impl KeyDomain for TestDomain {}
743 /// type TestKey = Key<TestDomain>;
744 ///
745 /// let key1 = TestKey::new("example")?;
746 /// let key2 = TestKey::new("example")?;
747 /// let key3 = TestKey::new("different")?;
748 ///
749 /// // Same keys have same hash
750 /// assert_eq!(key1.hash(), key2.hash());
751 /// // Different keys have different hashes (with high probability)
752 /// assert_ne!(key1.hash(), key3.hash());
753 /// # Ok::<(), domain_key::KeyParseError>(())
754 /// ```
755 #[inline]
756 #[must_use]
757 pub const fn hash(&self) -> u64 {
758 self.hash
759 }
760
761 /// Checks if this key starts with the given prefix
762 ///
763 /// This is a simple string prefix check that can be useful for
764 /// categorizing or filtering keys.
765 ///
766 /// # Arguments
767 ///
768 /// * `prefix` - The prefix string to check for
769 ///
770 /// # Examples
771 ///
772 /// ```rust
773 /// use domain_key::{Key, Domain, KeyDomain};
774 ///
775 /// #[derive(Debug)]
776 /// struct TestDomain;
777 /// impl Domain for TestDomain {
778 /// const DOMAIN_NAME: &'static str = "test";
779 /// }
780 /// impl KeyDomain for TestDomain {}
781 /// type TestKey = Key<TestDomain>;
782 ///
783 /// let key = TestKey::new("user_profile")?;
784 /// assert!(key.starts_with("user_"));
785 /// assert!(!key.starts_with("admin_"));
786 /// # Ok::<(), domain_key::KeyParseError>(())
787 /// ```
788 #[inline]
789 #[must_use]
790 pub fn starts_with(&self, prefix: &str) -> bool {
791 self.inner.starts_with(prefix)
792 }
793
794 /// Checks if this key ends with the given suffix
795 ///
796 /// This is a simple string suffix check that can be useful for
797 /// categorizing or filtering keys.
798 ///
799 /// # Arguments
800 ///
801 /// * `suffix` - The suffix string to check for
802 ///
803 /// # Examples
804 ///
805 /// ```rust
806 /// use domain_key::{Key, Domain, KeyDomain};
807 ///
808 /// #[derive(Debug)]
809 /// struct TestDomain;
810 /// impl Domain for TestDomain {
811 /// const DOMAIN_NAME: &'static str = "test";
812 /// }
813 /// impl KeyDomain for TestDomain {}
814 /// type TestKey = Key<TestDomain>;
815 ///
816 /// let key = TestKey::new("user_profile")?;
817 /// assert!(key.ends_with("_profile"));
818 /// assert!(!key.ends_with("_settings"));
819 /// # Ok::<(), domain_key::KeyParseError>(())
820 /// ```
821 #[inline]
822 #[must_use]
823 pub fn ends_with(&self, suffix: &str) -> bool {
824 self.inner.ends_with(suffix)
825 }
826
827 /// Checks if this key contains the given substring
828 ///
829 /// This performs a substring search within the key.
830 ///
831 /// # Arguments
832 ///
833 /// * `pattern` - The substring to search for
834 ///
835 /// # Examples
836 ///
837 /// ```rust
838 /// use domain_key::{Key, Domain, KeyDomain};
839 ///
840 /// #[derive(Debug)]
841 /// struct TestDomain;
842 /// impl Domain for TestDomain {
843 /// const DOMAIN_NAME: &'static str = "test";
844 /// }
845 /// impl KeyDomain for TestDomain {}
846 /// type TestKey = Key<TestDomain>;
847 ///
848 /// let key = TestKey::new("user_profile_settings")?;
849 /// assert!(key.contains("profile"));
850 /// assert!(!key.contains("admin"));
851 /// # Ok::<(), domain_key::KeyParseError>(())
852 /// ```
853 #[inline]
854 #[must_use]
855 pub fn contains(&self, pattern: &str) -> bool {
856 self.inner.contains(pattern)
857 }
858
859 /// Returns an iterator over the characters of the key
860 ///
861 /// This provides access to individual characters in the key string.
862 ///
863 /// # Examples
864 ///
865 /// ```rust
866 /// use domain_key::{Key, Domain, KeyDomain};
867 ///
868 /// #[derive(Debug)]
869 /// struct TestDomain;
870 /// impl Domain for TestDomain {
871 /// const DOMAIN_NAME: &'static str = "test";
872 /// }
873 /// impl KeyDomain for TestDomain {}
874 /// type TestKey = Key<TestDomain>;
875 ///
876 /// let key = TestKey::new("abc")?;
877 /// let chars: Vec<char> = key.chars().collect();
878 /// assert_eq!(chars, vec!['a', 'b', 'c']);
879 /// # Ok::<(), domain_key::KeyParseError>(())
880 /// ```
881 #[inline]
882 pub fn chars(&self) -> core::str::Chars<'_> {
883 self.inner.chars()
884 }
885
886 /// Splits the key by a delimiter and returns an iterator
887 ///
888 /// This method provides consistent split functionality.
889 ///
890 /// # Arguments
891 ///
892 /// * `delimiter` - Character to split on
893 ///
894 /// # Examples
895 ///
896 /// ```rust
897 /// use domain_key::{Key, Domain, KeyDomain};
898 ///
899 /// #[derive(Debug)]
900 /// struct TestDomain;
901 /// impl Domain for TestDomain {
902 /// const DOMAIN_NAME: &'static str = "test";
903 /// }
904 /// impl KeyDomain for TestDomain {}
905 /// type TestKey = Key<TestDomain>;
906 ///
907 /// let key = TestKey::new("user_profile_settings")?;
908 /// let parts: Vec<&str> = key.split('_').collect();
909 /// assert_eq!(parts, vec!["user", "profile", "settings"]);
910 /// # Ok::<(), domain_key::KeyParseError>(())
911 /// ```
912 #[must_use]
913 pub fn split(&self, delimiter: char) -> SplitIterator<'_> {
914 SplitIterator(utils::new_split_cache(&self.inner, delimiter))
915 }
916
917 /// Split operation for consistent API
918 ///
919 /// This method provides the same functionality as `split()` but with explicit naming
920 /// for cases where caching behavior needs to be clear.
921 #[must_use]
922 pub fn split_cached(&self, delimiter: char) -> SplitCache<'_> {
923 utils::new_split_cache(&self.inner, delimiter)
924 }
925
926 /// Splits the key by a string delimiter and returns an iterator
927 ///
928 /// This method splits the key using a string pattern rather than a single character.
929 ///
930 /// # Examples
931 ///
932 /// ```rust
933 /// use domain_key::{Key, Domain, KeyDomain};
934 ///
935 /// #[derive(Debug)]
936 /// struct TestDomain;
937 /// impl Domain for TestDomain {
938 /// const DOMAIN_NAME: &'static str = "test";
939 /// }
940 /// impl KeyDomain for TestDomain {}
941 /// type TestKey = Key<TestDomain>;
942 ///
943 /// let key = TestKey::new("user-and-profile-and-settings")?;
944 /// let parts: Vec<&str> = key.split_str("-and-").collect();
945 /// assert_eq!(parts, vec!["user", "profile", "settings"]);
946 /// # Ok::<(), domain_key::KeyParseError>(())
947 /// ```
948 #[must_use]
949 pub fn split_str<'a>(&'a self, delimiter: &'a str) -> core::str::Split<'a, &'a str> {
950 self.inner.split(delimiter)
951 }
952
953 /// Returns the key with a prefix if it doesn't already have it
954 ///
955 /// This method efficiently adds a prefix to a key if it doesn't already
956 /// start with that prefix.
957 ///
958 /// # Arguments
959 ///
960 /// * `prefix` - The prefix to ensure is present
961 ///
962 /// # Examples
963 ///
964 /// ```rust
965 /// use domain_key::{Key, Domain, KeyDomain};
966 ///
967 /// #[derive(Debug)]
968 /// struct TestDomain;
969 /// impl Domain for TestDomain {
970 /// const DOMAIN_NAME: &'static str = "test";
971 /// }
972 /// impl KeyDomain for TestDomain {}
973 /// type TestKey = Key<TestDomain>;
974 ///
975 /// let key = TestKey::new("profile")?;
976 /// let prefixed = key.ensure_prefix("user_")?;
977 /// assert_eq!(prefixed.as_str(), "user_profile");
978 ///
979 /// // If prefix already exists, returns the same key
980 /// let already_prefixed = prefixed.ensure_prefix("user_")?;
981 /// assert_eq!(already_prefixed.as_str(), "user_profile");
982 /// # Ok::<(), domain_key::KeyParseError>(())
983 /// ```
984 /// # Errors
985 ///
986 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
987 pub fn ensure_prefix(&self, prefix: &str) -> Result<Self, KeyParseError> {
988 if self.starts_with(prefix) {
989 return Ok(self.clone());
990 }
991
992 let new_len = prefix.len() + self.len();
993 if new_len > T::MAX_LENGTH {
994 return Err(KeyParseError::TooLong {
995 max_length: T::MAX_LENGTH,
996 actual_length: new_len,
997 });
998 }
999
1000 let result = utils::add_prefix_optimized(&self.inner, prefix);
1001
1002 // Quick validation of prefix only
1003 for (i, c) in prefix.chars().enumerate() {
1004 if !T::allowed_characters(c) {
1005 return Err(KeyParseError::InvalidCharacter {
1006 character: c,
1007 position: i,
1008 expected: Some("allowed by domain"),
1009 });
1010 }
1011 }
1012
1013 T::validate_domain_rules(&result).map_err(Self::fix_domain_error)?;
1014
1015 let hash = Self::compute_hash(&result);
1016 let length = u32::try_from(new_len).map_err(|_| KeyParseError::TooLong {
1017 max_length: u32::MAX as usize,
1018 actual_length: new_len,
1019 })?;
1020
1021 Ok(Self {
1022 inner: result,
1023 hash,
1024 length,
1025 _marker: PhantomData,
1026 })
1027 }
1028
1029 /// Returns the key with a suffix if it doesn't already have it
1030 ///
1031 /// This method efficiently adds a suffix to a key if it doesn't already
1032 /// end with that suffix.
1033 ///
1034 /// # Arguments
1035 ///
1036 /// * `suffix` - The suffix to ensure is present
1037 ///
1038 /// # Examples
1039 ///
1040 /// ```rust
1041 /// use domain_key::{Key, Domain, KeyDomain};
1042 ///
1043 /// #[derive(Debug)]
1044 /// struct TestDomain;
1045 /// impl Domain for TestDomain {
1046 /// const DOMAIN_NAME: &'static str = "test";
1047 /// }
1048 /// impl KeyDomain for TestDomain {}
1049 /// type TestKey = Key<TestDomain>;
1050 ///
1051 /// let key = TestKey::new("user")?;
1052 /// let suffixed = key.ensure_suffix("_profile")?;
1053 /// assert_eq!(suffixed.as_str(), "user_profile");
1054 ///
1055 /// // If suffix already exists, returns the same key
1056 /// let already_suffixed = suffixed.ensure_suffix("_profile")?;
1057 /// assert_eq!(already_suffixed.as_str(), "user_profile");
1058 /// # Ok::<(), domain_key::KeyParseError>(())
1059 /// ```
1060 /// # Errors
1061 ///
1062 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
1063 pub fn ensure_suffix(&self, suffix: &str) -> Result<Self, KeyParseError> {
1064 if self.ends_with(suffix) {
1065 return Ok(self.clone());
1066 }
1067
1068 let new_len = self.len() + suffix.len();
1069 if new_len > T::MAX_LENGTH {
1070 return Err(KeyParseError::TooLong {
1071 max_length: T::MAX_LENGTH,
1072 actual_length: new_len,
1073 });
1074 }
1075
1076 let result = utils::add_suffix_optimized(&self.inner, suffix);
1077
1078 // Quick validation of suffix only
1079 for (i, c) in suffix.chars().enumerate() {
1080 if !T::allowed_characters(c) {
1081 return Err(KeyParseError::InvalidCharacter {
1082 character: c,
1083 position: self.len() + i,
1084 expected: Some("allowed by domain"),
1085 });
1086 }
1087 }
1088
1089 T::validate_domain_rules(&result).map_err(Self::fix_domain_error)?;
1090
1091 let hash = Self::compute_hash(&result);
1092 let length = new_len.try_into().map_err(|_| KeyParseError::TooLong {
1093 max_length: u32::MAX as usize,
1094 actual_length: new_len,
1095 })?;
1096
1097 Ok(Self {
1098 inner: result,
1099 hash,
1100 length,
1101 _marker: PhantomData,
1102 })
1103 }
1104
1105 /// Get validation rules that this key satisfies
1106 ///
1107 /// Returns detailed information about the validation characteristics
1108 /// of this key and its domain, useful for debugging and introspection.
1109 ///
1110 /// # Examples
1111 ///
1112 /// ```rust
1113 /// use domain_key::{Key, Domain, KeyDomain};
1114 ///
1115 /// #[derive(Debug)]
1116 /// struct TestDomain;
1117 /// impl Domain for TestDomain {
1118 /// const DOMAIN_NAME: &'static str = "test";
1119 /// }
1120 /// impl KeyDomain for TestDomain {
1121 /// const MAX_LENGTH: usize = 32;
1122 /// const HAS_CUSTOM_VALIDATION: bool = true;
1123 /// }
1124 /// type TestKey = Key<TestDomain>;
1125 ///
1126 /// let key = TestKey::new("example")?;
1127 /// let info = key.validation_info();
1128 ///
1129 /// assert_eq!(info.domain_info.name, "test");
1130 /// assert_eq!(info.domain_info.max_length, 32);
1131 /// assert_eq!(info.length, 7);
1132 /// assert!(info.domain_info.has_custom_validation);
1133 /// # Ok::<(), domain_key::KeyParseError>(())
1134 /// ```
1135 #[must_use]
1136 pub fn validation_info(&self) -> KeyValidationInfo {
1137 KeyValidationInfo {
1138 domain_info: crate::domain::domain_info::<T>(),
1139 length: self.len(),
1140 }
1141 }
1142}
1143
1144// ============================================================================
1145// KEY IMPLEMENTATION - HELPER METHODS
1146// ============================================================================
1147
1148impl<T: KeyDomain> Key<T> {
1149 /// Fix domain name in domain validation errors
1150 ///
1151 /// This helper ensures that domain validation errors have the correct
1152 /// domain name, even when they're created generically.
1153 #[inline]
1154 fn fix_domain_error(e: KeyParseError) -> KeyParseError {
1155 match e {
1156 KeyParseError::DomainValidation { message, .. } => KeyParseError::DomainValidation {
1157 domain: T::DOMAIN_NAME,
1158 message,
1159 },
1160 other => other,
1161 }
1162 }
1163
1164 /// Common validation pipeline
1165 ///
1166 /// Performs validation that's common to all domains: length checking,
1167 /// character validation, and structural validation.
1168 ///
1169 /// # Errors
1170 ///
1171 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
1172 pub(crate) fn validate_common(key: &str) -> Result<(), KeyParseError> {
1173 let trimmed = key.trim();
1174
1175 if trimmed.is_empty() {
1176 return Err(KeyParseError::Empty);
1177 }
1178
1179 if trimmed.len() > T::MAX_LENGTH {
1180 return Err(KeyParseError::TooLong {
1181 max_length: T::MAX_LENGTH,
1182 actual_length: trimmed.len(),
1183 });
1184 }
1185
1186 if trimmed.len() < T::min_length() {
1187 return Err(KeyParseError::InvalidStructure {
1188 reason: "key is shorter than minimum required length",
1189 });
1190 }
1191
1192 // Use fast validation
1193 Self::validate_fast(trimmed)
1194 }
1195
1196 /// Fast validation path using optimized algorithms
1197 /// # Errors
1198 ///
1199 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
1200 fn validate_fast(key: &str) -> Result<(), KeyParseError> {
1201 let mut chars = key.char_indices();
1202 let mut prev_char = None;
1203
1204 // Validate first character
1205 if let Some((pos, first)) = chars.next() {
1206 let char_allowed = crate::utils::char_validation::is_key_char_fast(first)
1207 || T::allowed_start_character(first);
1208
1209 if !char_allowed {
1210 return Err(KeyParseError::InvalidCharacter {
1211 character: first,
1212 position: pos,
1213 expected: Some("allowed by domain"),
1214 });
1215 }
1216
1217 prev_char = Some(first);
1218 }
1219
1220 // Validate remaining characters
1221 for (pos, c) in chars {
1222 let char_allowed =
1223 crate::utils::char_validation::is_key_char_fast(c) || T::allowed_characters(c);
1224
1225 if !char_allowed {
1226 return Err(KeyParseError::InvalidCharacter {
1227 character: c,
1228 position: pos,
1229 expected: Some("allowed by domain"),
1230 });
1231 }
1232
1233 if let Some(prev) = prev_char {
1234 if !T::allowed_consecutive_characters(prev, c) {
1235 return Err(KeyParseError::InvalidStructure {
1236 reason: "consecutive characters not allowed",
1237 });
1238 }
1239 }
1240 prev_char = Some(c);
1241 }
1242
1243 // Check last character
1244 if let Some(last) = prev_char {
1245 if !T::allowed_end_character(last) {
1246 return Err(KeyParseError::InvalidStructure {
1247 reason: "invalid end character",
1248 });
1249 }
1250 }
1251
1252 Ok(())
1253 }
1254
1255 /// Normalize a borrowed string
1256 pub(crate) fn normalize(key: &str) -> Cow<'_, str> {
1257 let trimmed = key.trim();
1258
1259 let needs_lowercase =
1260 T::CASE_INSENSITIVE && trimmed.chars().any(|c| c.is_ascii_uppercase());
1261
1262 let base = if needs_lowercase {
1263 Cow::Owned(trimmed.to_ascii_lowercase())
1264 } else {
1265 // Borrow the trimmed slice — no allocation needed
1266 Cow::Borrowed(trimmed)
1267 };
1268
1269 // Apply domain-specific normalization
1270 T::normalize_domain(base)
1271 }
1272
1273 /// Normalize an owned string efficiently
1274 fn normalize_owned(mut key: String) -> String {
1275 // In-place trim: remove leading whitespace by draining, then truncate trailing
1276 let start = key.len() - key.trim_start().len();
1277 if start > 0 {
1278 key.drain(..start);
1279 }
1280 let trimmed_len = key.trim_end().len();
1281 key.truncate(trimmed_len);
1282
1283 if T::CASE_INSENSITIVE {
1284 key.make_ascii_lowercase();
1285 }
1286
1287 // Apply domain normalization
1288 match T::normalize_domain(Cow::Owned(key)) {
1289 Cow::Owned(s) => s,
1290 Cow::Borrowed(_) => unreachable!("We passed Cow::Owned"),
1291 }
1292 }
1293
1294 /// Compute hash using the configured algorithm
1295 ///
1296 /// The hash algorithm is selected at compile time based on feature flags,
1297 /// allowing for different performance/security trade-offs.
1298 pub(crate) fn compute_hash(key: &str) -> u64 {
1299 if key.is_empty() {
1300 return 0;
1301 }
1302
1303 Self::compute_hash_inner(key.as_bytes())
1304 }
1305
1306 /// Inner hash computation dispatched by feature flags
1307 ///
1308 /// Separated to keep each cfg branch as the sole return path,
1309 /// avoiding mixed `return` statements and dead-code warnings.
1310 fn compute_hash_inner(bytes: &[u8]) -> u64 {
1311 // Priority: fast > secure > crypto > default
1312
1313 #[cfg(feature = "fast")]
1314 {
1315 #[cfg(any(
1316 all(target_arch = "x86_64", target_feature = "aes"),
1317 all(
1318 target_arch = "aarch64",
1319 target_feature = "aes",
1320 target_feature = "neon"
1321 )
1322 ))]
1323 {
1324 gxhash::gxhash64(bytes, 0)
1325 }
1326
1327 #[cfg(not(any(
1328 all(target_arch = "x86_64", target_feature = "aes"),
1329 all(
1330 target_arch = "aarch64",
1331 target_feature = "aes",
1332 target_feature = "neon"
1333 )
1334 )))]
1335 {
1336 use core::hash::Hasher;
1337 let mut hasher = ahash::AHasher::default();
1338 hasher.write(bytes);
1339 hasher.finish()
1340 }
1341 }
1342
1343 #[cfg(all(feature = "secure", not(feature = "fast")))]
1344 {
1345 use core::hash::Hasher;
1346 let mut hasher = ahash::AHasher::default();
1347 hasher.write(bytes);
1348 hasher.finish()
1349 }
1350
1351 #[cfg(all(feature = "crypto", not(any(feature = "fast", feature = "secure"))))]
1352 {
1353 let hash = blake3::hash(bytes);
1354 let h = hash.as_bytes();
1355 u64::from_le_bytes([h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]])
1356 }
1357
1358 #[cfg(not(any(feature = "fast", feature = "secure", feature = "crypto")))]
1359 {
1360 #[cfg(feature = "std")]
1361 {
1362 use core::hash::Hasher;
1363 use std::collections::hash_map::DefaultHasher;
1364 let mut hasher = DefaultHasher::new();
1365 hasher.write(bytes);
1366 hasher.finish()
1367 }
1368
1369 #[cfg(not(feature = "std"))]
1370 {
1371 Self::fnv1a_hash(bytes)
1372 }
1373 }
1374 }
1375
1376 /// FNV-1a hash implementation for `no_std` environments
1377 #[allow(dead_code)]
1378 fn fnv1a_hash(bytes: &[u8]) -> u64 {
1379 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
1380 const FNV_PRIME: u64 = 0x0100_0000_01b3;
1381
1382 let mut hash = FNV_OFFSET_BASIS;
1383 for &byte in bytes {
1384 hash ^= u64::from(byte);
1385 hash = hash.wrapping_mul(FNV_PRIME);
1386 }
1387 hash
1388 }
1389}
1390
1391// ============================================================================
1392// SUPPORTING TYPES
1393// ============================================================================
1394
1395/// Information about a key's validation characteristics
1396///
1397/// This structure provides detailed information about how a key was validated
1398/// and what domain-specific features are enabled. Domain-level configuration
1399/// is available through the embedded [`DomainInfo`](crate::DomainInfo).
1400#[derive(Debug, Clone, PartialEq, Eq)]
1401pub struct KeyValidationInfo {
1402 /// Full domain configuration
1403 pub domain_info: crate::domain::DomainInfo,
1404 /// Actual length of the key
1405 pub length: usize,
1406}
1407
1408// ============================================================================
1409// STANDARD TRAIT IMPLEMENTATIONS
1410// ============================================================================
1411
1412/// Display implementation shows the key value
1413///
1414/// Outputs just the key string, consistent with `AsRef<str>`, `From<Key<T>> for String`,
1415/// and serde serialization. Use [`Key::domain`] separately when domain context is needed.
1416impl<T: KeyDomain> fmt::Display for Key<T> {
1417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1418 f.write_str(&self.inner)
1419 }
1420}
1421
1422/// `AsRef` implementation for string conversion
1423impl<T: KeyDomain> AsRef<str> for Key<T> {
1424 #[inline]
1425 fn as_ref(&self) -> &str {
1426 &self.inner
1427 }
1428}
1429
1430/// From implementation for converting to String
1431impl<T: KeyDomain> From<Key<T>> for String {
1432 fn from(key: Key<T>) -> Self {
1433 key.inner.into()
1434 }
1435}
1436
1437/// `TryFrom<String>` implementation for owned string conversion
1438///
1439/// This avoids re-borrowing through `&str` when you already have a `String`.
1440impl<T: KeyDomain> TryFrom<String> for Key<T> {
1441 type Error = KeyParseError;
1442
1443 fn try_from(s: String) -> Result<Self, Self::Error> {
1444 Key::from_string(s)
1445 }
1446}
1447
1448/// `TryFrom<&str>` implementation for borrowed string conversion
1449impl<T: KeyDomain> TryFrom<&str> for Key<T> {
1450 type Error = KeyParseError;
1451
1452 fn try_from(s: &str) -> Result<Self, Self::Error> {
1453 Key::new(s)
1454 }
1455}
1456
1457/// `FromStr` implementation for parsing from strings
1458impl<T: KeyDomain> FromStr for Key<T> {
1459 type Err = KeyParseError;
1460
1461 fn from_str(s: &str) -> Result<Self, Self::Err> {
1462 Key::new(s)
1463 }
1464}
1465
1466// ============================================================================
1467// TESTS
1468// ============================================================================
1469
1470#[cfg(test)]
1471mod tests {
1472 use super::*;
1473 use crate::domain::{DefaultDomain, Domain};
1474 #[cfg(not(feature = "std"))]
1475 use alloc::format;
1476 #[cfg(not(feature = "std"))]
1477 use alloc::string::ToString;
1478 #[cfg(not(feature = "std"))]
1479 use alloc::vec;
1480 #[cfg(not(feature = "std"))]
1481 use alloc::vec::Vec;
1482
1483 // Test domain
1484 #[derive(Debug)]
1485 struct TestDomain;
1486
1487 impl Domain for TestDomain {
1488 const DOMAIN_NAME: &'static str = "test";
1489 }
1490
1491 impl KeyDomain for TestDomain {
1492 const MAX_LENGTH: usize = 32;
1493 const HAS_CUSTOM_VALIDATION: bool = true;
1494 const HAS_CUSTOM_NORMALIZATION: bool = true;
1495 const CASE_INSENSITIVE: bool = true;
1496
1497 fn validate_domain_rules(key: &str) -> Result<(), KeyParseError> {
1498 if key.starts_with("invalid_") {
1499 return Err(KeyParseError::domain_error(
1500 Self::DOMAIN_NAME,
1501 "Keys cannot start with 'invalid_'",
1502 ));
1503 }
1504 Ok(())
1505 }
1506
1507 fn normalize_domain(key: Cow<'_, str>) -> Cow<'_, str> {
1508 if key.contains('-') {
1509 Cow::Owned(key.replace('-', "_"))
1510 } else {
1511 key
1512 }
1513 }
1514
1515 fn allowed_characters(c: char) -> bool {
1516 c.is_ascii_alphanumeric() || c == '_' || c == '-'
1517 }
1518
1519 fn validation_help() -> Option<&'static str> {
1520 Some("Use alphanumeric characters, underscores, and hyphens. Cannot start with 'invalid_'.")
1521 }
1522 }
1523
1524 type TestKey = Key<TestDomain>;
1525
1526 #[test]
1527 fn new_key_stores_value_and_domain() {
1528 let key = TestKey::new("valid_key").unwrap();
1529 assert_eq!(key.as_str(), "valid_key");
1530 assert_eq!(key.domain(), "test");
1531 assert_eq!(key.len(), 9);
1532 }
1533
1534 #[test]
1535 fn case_insensitive_domain_lowercases_and_normalizes() {
1536 let key = TestKey::new("Test-Key").unwrap();
1537 assert_eq!(key.as_str(), "test_key");
1538 }
1539
1540 #[test]
1541 fn domain_rules_reject_invalid_prefix() {
1542 let result = TestKey::new("invalid_key");
1543 assert!(result.is_err());
1544
1545 if let Err(KeyParseError::DomainValidation { domain, message }) = result {
1546 assert_eq!(domain, "test");
1547 assert!(message.contains("invalid_"));
1548 } else {
1549 panic!("Expected domain validation error");
1550 }
1551 }
1552
1553 #[test]
1554 fn rejects_empty_too_long_and_invalid_characters() {
1555 // Empty key
1556 assert!(matches!(TestKey::new(""), Err(KeyParseError::Empty)));
1557
1558 // Too long key
1559 let long_key = "a".repeat(50);
1560 assert!(matches!(
1561 TestKey::new(&long_key),
1562 Err(KeyParseError::TooLong {
1563 max_length: 32,
1564 actual_length: 50
1565 })
1566 ));
1567
1568 // Invalid character
1569 let result = TestKey::new("key with spaces");
1570 assert!(matches!(
1571 result,
1572 Err(KeyParseError::InvalidCharacter {
1573 character: ' ',
1574 position: 3,
1575 ..
1576 })
1577 ));
1578 }
1579
1580 #[test]
1581 fn equal_keys_produce_same_hash() {
1582 let key1 = TestKey::new("test_key").unwrap();
1583 let key2 = TestKey::new("test_key").unwrap();
1584
1585 // Same keys should have same hash
1586 assert_eq!(key1.hash(), key2.hash());
1587
1588 let key3 = TestKey::new("different_key").unwrap();
1589 // Different keys should have different hashes (with high probability)
1590 assert_ne!(key1.hash(), key3.hash());
1591 }
1592
1593 #[test]
1594 fn string_query_methods_work_correctly() {
1595 let key = TestKey::new("test_key_example").unwrap();
1596 assert!(key.starts_with("test_"));
1597 assert!(key.ends_with("_example"));
1598 assert!(key.contains("_key_"));
1599 assert_eq!(key.len(), 16);
1600 assert!(!key.is_empty());
1601 }
1602
1603 #[test]
1604 fn from_string_validates_owned_input() {
1605 let key = TestKey::from_string("test_key".to_string()).unwrap();
1606 assert_eq!(key.as_str(), "test_key");
1607 }
1608
1609 #[test]
1610 fn try_from_static_rejects_empty_string() {
1611 let key = TestKey::try_from_static("static_key").unwrap();
1612 assert_eq!(key.as_str(), "static_key");
1613
1614 let invalid = TestKey::try_from_static("");
1615 assert!(invalid.is_err());
1616 }
1617
1618 #[test]
1619 fn validation_info_reflects_domain_config() {
1620 let key = TestKey::new("test_key").unwrap();
1621 let info = key.validation_info();
1622
1623 assert_eq!(info.domain_info.name, "test");
1624 assert_eq!(info.domain_info.max_length, 32);
1625 assert_eq!(info.length, 8);
1626 assert!(info.domain_info.has_custom_validation);
1627 assert!(info.domain_info.has_custom_normalization);
1628 }
1629
1630 #[cfg(feature = "serde")]
1631 #[test]
1632 fn serde_roundtrip_preserves_key() {
1633 let key = TestKey::new("test_key").unwrap();
1634
1635 // Test JSON serialization
1636 let json = serde_json::to_string(&key).unwrap();
1637 assert_eq!(json, r#""test_key""#);
1638
1639 // Test JSON deserialization
1640 let deserialized: TestKey = serde_json::from_str(&json).unwrap();
1641 assert_eq!(deserialized, key);
1642 }
1643
1644 #[test]
1645 fn from_parts_joins_and_splits_roundtrip() {
1646 let key = TestKey::from_parts(&["user", "123", "profile"], "_").unwrap();
1647 assert_eq!(key.as_str(), "user_123_profile");
1648
1649 let parts: Vec<&str> = key.split('_').collect();
1650 assert_eq!(parts, vec!["user", "123", "profile"]);
1651 }
1652
1653 #[test]
1654 fn ensure_prefix_suffix_is_idempotent() {
1655 let key = TestKey::new("profile").unwrap();
1656
1657 let prefixed = key.ensure_prefix("user_").unwrap();
1658 assert_eq!(prefixed.as_str(), "user_profile");
1659
1660 // Already has prefix
1661 let same = prefixed.ensure_prefix("user_").unwrap();
1662 assert_eq!(same.as_str(), "user_profile");
1663
1664 let suffixed = key.ensure_suffix("_v1").unwrap();
1665 assert_eq!(suffixed.as_str(), "profile_v1");
1666
1667 // Already has suffix
1668 let same = suffixed.ensure_suffix("_v1").unwrap();
1669 assert_eq!(same.as_str(), "profile_v1");
1670 }
1671
1672 #[test]
1673 fn display_shows_raw_key_value() {
1674 let key = TestKey::new("example").unwrap();
1675 assert_eq!(format!("{key}"), "example");
1676 }
1677
1678 #[test]
1679 fn into_string_extracts_value() {
1680 let key = TestKey::new("example").unwrap();
1681 let string: String = key.into();
1682 assert_eq!(string, "example");
1683 }
1684
1685 #[test]
1686 fn parse_str_creates_validated_key() {
1687 let key: TestKey = "example".parse().unwrap();
1688 assert_eq!(key.as_str(), "example");
1689 }
1690
1691 #[test]
1692 fn default_domain_accepts_simple_keys() {
1693 type DefaultKey = Key<DefaultDomain>;
1694 let key = DefaultKey::new("test_key").unwrap();
1695 assert_eq!(key.domain(), "default");
1696 assert_eq!(key.as_str(), "test_key");
1697 }
1698
1699 #[test]
1700 fn len_returns_consistent_cached_value() {
1701 let key = TestKey::new("test_key").unwrap();
1702 // Length should be cached and O(1)
1703 assert_eq!(key.len(), 8);
1704 assert_eq!(key.len(), 8); // Second call should use cache
1705 }
1706
1707 #[test]
1708 fn split_methods_produce_same_parts() {
1709 let key = TestKey::new("user_profile_settings").unwrap();
1710
1711 let parts: Vec<&str> = key.split('_').collect();
1712 assert_eq!(parts, vec!["user", "profile", "settings"]);
1713
1714 let cached_parts: Vec<&str> = key.split_cached('_').collect();
1715 assert_eq!(cached_parts, vec!["user", "profile", "settings"]);
1716
1717 let str_parts: Vec<&str> = key.split_str("_").collect();
1718 assert_eq!(str_parts, vec!["user", "profile", "settings"]);
1719 }
1720}