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 #[expect(
523 clippy::cast_possible_truncation,
524 reason = "key length validated to fit in u32"
525 )]
526 let length = key.len() as u32;
527
528 Self {
529 inner: SmartString::from(key),
530 hash,
531 length,
532 _marker: PhantomData,
533 }
534 }
535
536 /// Creates a key from a static string with validation
537 ///
538 /// This is a safer alternative to `from_static_unchecked` that validates
539 /// the key at runtime. The validation cost is paid once, and subsequent
540 /// uses of the key are as fast as the unchecked version.
541 ///
542 /// # Arguments
543 ///
544 /// * `key` - A static string literal to validate and convert
545 ///
546 /// # Errors
547 ///
548 /// Returns `KeyParseError` if the static key fails validation
549 ///
550 /// # Examples
551 ///
552 /// ```rust
553 /// use domain_key::{Key, Domain, KeyDomain};
554 ///
555 /// #[derive(Debug)]
556 /// struct TestDomain;
557 /// impl Domain for TestDomain {
558 /// const DOMAIN_NAME: &'static str = "test";
559 /// }
560 /// impl KeyDomain for TestDomain {}
561 /// type TestKey = Key<TestDomain>;
562 ///
563 /// let key = TestKey::try_from_static("static_key")?;
564 /// assert_eq!(key.as_str(), "static_key");
565 ///
566 /// let invalid = TestKey::try_from_static("");
567 /// assert!(invalid.is_err());
568 /// # Ok::<(), domain_key::KeyParseError>(())
569 /// ```
570 /// # Errors
571 ///
572 /// Returns `KeyParseError` if the constructed key fails validation
573 pub fn try_from_static(key: &'static str) -> Result<Self, KeyParseError> {
574 // Validate and create via the normal path
575 Self::new(key)
576 }
577
578 /// Try to create a key, returning None on validation failure
579 ///
580 /// This is a convenience method for when you want to handle validation
581 /// failures by ignoring invalid keys rather than handling errors.
582 ///
583 /// # Examples
584 ///
585 /// ```rust
586 /// use domain_key::{Key, Domain, KeyDomain};
587 ///
588 /// #[derive(Debug)]
589 /// struct TestDomain;
590 /// impl Domain for TestDomain {
591 /// const DOMAIN_NAME: &'static str = "test";
592 /// }
593 /// impl KeyDomain for TestDomain {}
594 /// type TestKey = Key<TestDomain>;
595 ///
596 /// let valid = TestKey::try_new("valid_key").unwrap();
597 /// let invalid = TestKey::try_new(""); // Returns None
598 /// assert!(invalid.is_none());
599 /// ```
600 #[inline]
601 pub fn try_new(key: impl AsRef<str>) -> Option<Self> {
602 Self::new(key).ok()
603 }
604}
605
606// ============================================================================
607// KEY IMPLEMENTATION - ACCESSOR METHODS
608// ============================================================================
609
610impl<T: KeyDomain> Key<T> {
611 /// Returns the key as a string slice
612 ///
613 /// This is the primary way to access the string content of a key.
614 /// The returned reference is valid for the lifetime of the key.
615 ///
616 /// # Examples
617 ///
618 /// ```rust
619 /// use domain_key::{Key, Domain, KeyDomain};
620 ///
621 /// #[derive(Debug)]
622 /// struct TestDomain;
623 /// impl Domain for TestDomain {
624 /// const DOMAIN_NAME: &'static str = "test";
625 /// }
626 /// impl KeyDomain for TestDomain {}
627 /// type TestKey = Key<TestDomain>;
628 ///
629 /// let key = TestKey::new("example")?;
630 /// assert_eq!(key.as_str(), "example");
631 /// # Ok::<(), domain_key::KeyParseError>(())
632 /// ```
633 #[inline]
634 #[must_use]
635 pub fn as_str(&self) -> &str {
636 &self.inner
637 }
638
639 /// Returns the domain name for this key type
640 ///
641 /// This is a compile-time constant that identifies which domain
642 /// this key belongs to.
643 ///
644 /// # Examples
645 ///
646 /// ```rust
647 /// use domain_key::{Key, Domain, KeyDomain};
648 ///
649 /// #[derive(Debug)]
650 /// struct UserDomain;
651 /// impl Domain for UserDomain {
652 /// const DOMAIN_NAME: &'static str = "user";
653 /// }
654 /// impl KeyDomain for UserDomain {}
655 /// type UserKey = Key<UserDomain>;
656 ///
657 /// let key = UserKey::new("john")?;
658 /// assert_eq!(key.domain(), "user");
659 /// # Ok::<(), domain_key::KeyParseError>(())
660 /// ```
661 #[inline]
662 #[must_use]
663 pub const fn domain(&self) -> &'static str {
664 T::DOMAIN_NAME
665 }
666
667 /// Returns the length of the key string
668 ///
669 /// This is an O(1) operation using a cached length value.
670 ///
671 /// # Examples
672 ///
673 /// ```rust
674 /// use domain_key::{Key, Domain, KeyDomain};
675 ///
676 /// #[derive(Debug)]
677 /// struct TestDomain;
678 /// impl Domain for TestDomain {
679 /// const DOMAIN_NAME: &'static str = "test";
680 /// }
681 /// impl KeyDomain for TestDomain {}
682 /// type TestKey = Key<TestDomain>;
683 ///
684 /// let key = TestKey::new("example")?;
685 /// assert_eq!(key.len(), 7);
686 /// # Ok::<(), domain_key::KeyParseError>(())
687 /// ```
688 #[inline]
689 #[must_use]
690 pub fn len(&self) -> usize {
691 self.length as usize
692 }
693
694 /// Returns true if the key is empty (this should never happen for valid keys)
695 ///
696 /// Since empty keys are rejected during validation, this method should
697 /// always return `false` for properly constructed keys. It's provided
698 /// for completeness and debugging purposes.
699 ///
700 /// # Examples
701 ///
702 /// ```rust
703 /// use domain_key::{Key, Domain, KeyDomain};
704 ///
705 /// #[derive(Debug)]
706 /// struct TestDomain;
707 /// impl Domain for TestDomain {
708 /// const DOMAIN_NAME: &'static str = "test";
709 /// }
710 /// impl KeyDomain for TestDomain {}
711 /// type TestKey = Key<TestDomain>;
712 ///
713 /// let key = TestKey::new("example")?;
714 /// assert!(!key.is_empty());
715 /// # Ok::<(), domain_key::KeyParseError>(())
716 /// ```
717 #[inline]
718 #[must_use]
719 pub fn is_empty(&self) -> bool {
720 self.length == 0
721 }
722
723 /// Returns the cached hash value
724 ///
725 /// This hash is computed once during key creation and cached for the
726 /// lifetime of the key. It's used internally for hash-based collections
727 /// and can be useful for custom hash-based data structures.
728 ///
729 /// **Note:** The hash algorithm depends on the active feature flags
730 /// (`fast`, `secure`, `crypto`, or the default hasher). Keys created
731 /// with different feature configurations will produce different hash
732 /// values. Do not persist or compare hash values across builds with
733 /// different features.
734 ///
735 /// # Examples
736 ///
737 /// ```rust
738 /// use domain_key::{Key, Domain, KeyDomain};
739 ///
740 /// #[derive(Debug)]
741 /// struct TestDomain;
742 /// impl Domain for TestDomain {
743 /// const DOMAIN_NAME: &'static str = "test";
744 /// }
745 /// impl KeyDomain for TestDomain {}
746 /// type TestKey = Key<TestDomain>;
747 ///
748 /// let key1 = TestKey::new("example")?;
749 /// let key2 = TestKey::new("example")?;
750 /// let key3 = TestKey::new("different")?;
751 ///
752 /// // Same keys have same hash
753 /// assert_eq!(key1.hash(), key2.hash());
754 /// // Different keys have different hashes (with high probability)
755 /// assert_ne!(key1.hash(), key3.hash());
756 /// # Ok::<(), domain_key::KeyParseError>(())
757 /// ```
758 #[inline]
759 #[must_use]
760 pub const fn hash(&self) -> u64 {
761 self.hash
762 }
763
764 /// Checks if this key starts with the given prefix
765 ///
766 /// This is a simple string prefix check that can be useful for
767 /// categorizing or filtering keys.
768 ///
769 /// # Arguments
770 ///
771 /// * `prefix` - The prefix string to check for
772 ///
773 /// # Examples
774 ///
775 /// ```rust
776 /// use domain_key::{Key, Domain, KeyDomain};
777 ///
778 /// #[derive(Debug)]
779 /// struct TestDomain;
780 /// impl Domain for TestDomain {
781 /// const DOMAIN_NAME: &'static str = "test";
782 /// }
783 /// impl KeyDomain for TestDomain {}
784 /// type TestKey = Key<TestDomain>;
785 ///
786 /// let key = TestKey::new("user_profile")?;
787 /// assert!(key.starts_with("user_"));
788 /// assert!(!key.starts_with("admin_"));
789 /// # Ok::<(), domain_key::KeyParseError>(())
790 /// ```
791 #[inline]
792 #[must_use]
793 pub fn starts_with(&self, prefix: &str) -> bool {
794 self.inner.starts_with(prefix)
795 }
796
797 /// Checks if this key ends with the given suffix
798 ///
799 /// This is a simple string suffix check that can be useful for
800 /// categorizing or filtering keys.
801 ///
802 /// # Arguments
803 ///
804 /// * `suffix` - The suffix string to check for
805 ///
806 /// # Examples
807 ///
808 /// ```rust
809 /// use domain_key::{Key, Domain, KeyDomain};
810 ///
811 /// #[derive(Debug)]
812 /// struct TestDomain;
813 /// impl Domain for TestDomain {
814 /// const DOMAIN_NAME: &'static str = "test";
815 /// }
816 /// impl KeyDomain for TestDomain {}
817 /// type TestKey = Key<TestDomain>;
818 ///
819 /// let key = TestKey::new("user_profile")?;
820 /// assert!(key.ends_with("_profile"));
821 /// assert!(!key.ends_with("_settings"));
822 /// # Ok::<(), domain_key::KeyParseError>(())
823 /// ```
824 #[inline]
825 #[must_use]
826 pub fn ends_with(&self, suffix: &str) -> bool {
827 self.inner.ends_with(suffix)
828 }
829
830 /// Checks if this key contains the given substring
831 ///
832 /// This performs a substring search within the key.
833 ///
834 /// # Arguments
835 ///
836 /// * `pattern` - The substring to search for
837 ///
838 /// # Examples
839 ///
840 /// ```rust
841 /// use domain_key::{Key, Domain, KeyDomain};
842 ///
843 /// #[derive(Debug)]
844 /// struct TestDomain;
845 /// impl Domain for TestDomain {
846 /// const DOMAIN_NAME: &'static str = "test";
847 /// }
848 /// impl KeyDomain for TestDomain {}
849 /// type TestKey = Key<TestDomain>;
850 ///
851 /// let key = TestKey::new("user_profile_settings")?;
852 /// assert!(key.contains("profile"));
853 /// assert!(!key.contains("admin"));
854 /// # Ok::<(), domain_key::KeyParseError>(())
855 /// ```
856 #[inline]
857 #[must_use]
858 pub fn contains(&self, pattern: &str) -> bool {
859 self.inner.contains(pattern)
860 }
861
862 /// Returns an iterator over the characters of the key
863 ///
864 /// This provides access to individual characters in the key string.
865 ///
866 /// # Examples
867 ///
868 /// ```rust
869 /// use domain_key::{Key, Domain, KeyDomain};
870 ///
871 /// #[derive(Debug)]
872 /// struct TestDomain;
873 /// impl Domain for TestDomain {
874 /// const DOMAIN_NAME: &'static str = "test";
875 /// }
876 /// impl KeyDomain for TestDomain {}
877 /// type TestKey = Key<TestDomain>;
878 ///
879 /// let key = TestKey::new("abc")?;
880 /// let chars: Vec<char> = key.chars().collect();
881 /// assert_eq!(chars, vec!['a', 'b', 'c']);
882 /// # Ok::<(), domain_key::KeyParseError>(())
883 /// ```
884 #[inline]
885 pub fn chars(&self) -> core::str::Chars<'_> {
886 self.inner.chars()
887 }
888
889 /// Splits the key by a delimiter and returns an iterator
890 ///
891 /// This method provides consistent split functionality.
892 ///
893 /// # Arguments
894 ///
895 /// * `delimiter` - Character to split on
896 ///
897 /// # Examples
898 ///
899 /// ```rust
900 /// use domain_key::{Key, Domain, KeyDomain};
901 ///
902 /// #[derive(Debug)]
903 /// struct TestDomain;
904 /// impl Domain for TestDomain {
905 /// const DOMAIN_NAME: &'static str = "test";
906 /// }
907 /// impl KeyDomain for TestDomain {}
908 /// type TestKey = Key<TestDomain>;
909 ///
910 /// let key = TestKey::new("user_profile_settings")?;
911 /// let parts: Vec<&str> = key.split('_').collect();
912 /// assert_eq!(parts, vec!["user", "profile", "settings"]);
913 /// # Ok::<(), domain_key::KeyParseError>(())
914 /// ```
915 #[must_use]
916 pub fn split(&self, delimiter: char) -> SplitIterator<'_> {
917 SplitIterator(utils::new_split_cache(&self.inner, delimiter))
918 }
919
920 /// Split operation for consistent API
921 ///
922 /// This method provides the same functionality as `split()` but with explicit naming
923 /// for cases where caching behavior needs to be clear.
924 #[must_use]
925 pub fn split_cached(&self, delimiter: char) -> SplitCache<'_> {
926 utils::new_split_cache(&self.inner, delimiter)
927 }
928
929 /// Splits the key by a string delimiter and returns an iterator
930 ///
931 /// This method splits the key using a string pattern rather than a single character.
932 ///
933 /// # Examples
934 ///
935 /// ```rust
936 /// use domain_key::{Key, Domain, KeyDomain};
937 ///
938 /// #[derive(Debug)]
939 /// struct TestDomain;
940 /// impl Domain for TestDomain {
941 /// const DOMAIN_NAME: &'static str = "test";
942 /// }
943 /// impl KeyDomain for TestDomain {}
944 /// type TestKey = Key<TestDomain>;
945 ///
946 /// let key = TestKey::new("user-and-profile-and-settings")?;
947 /// let parts: Vec<&str> = key.split_str("-and-").collect();
948 /// assert_eq!(parts, vec!["user", "profile", "settings"]);
949 /// # Ok::<(), domain_key::KeyParseError>(())
950 /// ```
951 #[must_use]
952 pub fn split_str<'a>(&'a self, delimiter: &'a str) -> core::str::Split<'a, &'a str> {
953 self.inner.split(delimiter)
954 }
955
956 /// Returns the key with a prefix if it doesn't already have it
957 ///
958 /// This method efficiently adds a prefix to a key if it doesn't already
959 /// start with that prefix.
960 ///
961 /// # Arguments
962 ///
963 /// * `prefix` - The prefix to ensure is present
964 ///
965 /// # Examples
966 ///
967 /// ```rust
968 /// use domain_key::{Key, Domain, KeyDomain};
969 ///
970 /// #[derive(Debug)]
971 /// struct TestDomain;
972 /// impl Domain for TestDomain {
973 /// const DOMAIN_NAME: &'static str = "test";
974 /// }
975 /// impl KeyDomain for TestDomain {}
976 /// type TestKey = Key<TestDomain>;
977 ///
978 /// let key = TestKey::new("profile")?;
979 /// let prefixed = key.ensure_prefix("user_")?;
980 /// assert_eq!(prefixed.as_str(), "user_profile");
981 ///
982 /// // If prefix already exists, returns the same key
983 /// let already_prefixed = prefixed.ensure_prefix("user_")?;
984 /// assert_eq!(already_prefixed.as_str(), "user_profile");
985 /// # Ok::<(), domain_key::KeyParseError>(())
986 /// ```
987 /// # Errors
988 ///
989 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
990 pub fn ensure_prefix(&self, prefix: &str) -> Result<Self, KeyParseError> {
991 if self.starts_with(prefix) {
992 return Ok(self.clone());
993 }
994
995 let new_len = prefix.len() + self.len();
996 if new_len > T::MAX_LENGTH {
997 return Err(KeyParseError::TooLong {
998 max_length: T::MAX_LENGTH,
999 actual_length: new_len,
1000 });
1001 }
1002
1003 let result = utils::add_prefix_optimized(&self.inner, prefix);
1004
1005 // Quick validation of prefix only
1006 for (i, c) in prefix.chars().enumerate() {
1007 if !T::allowed_characters(c) {
1008 return Err(KeyParseError::InvalidCharacter {
1009 character: c,
1010 position: i,
1011 expected: Some("allowed by domain"),
1012 });
1013 }
1014 }
1015
1016 T::validate_domain_rules(&result).map_err(Self::fix_domain_error)?;
1017
1018 let hash = Self::compute_hash(&result);
1019 let length = u32::try_from(new_len).map_err(|_| KeyParseError::TooLong {
1020 max_length: u32::MAX as usize,
1021 actual_length: new_len,
1022 })?;
1023
1024 Ok(Self {
1025 inner: result,
1026 hash,
1027 length,
1028 _marker: PhantomData,
1029 })
1030 }
1031
1032 /// Returns the key with a suffix if it doesn't already have it
1033 ///
1034 /// This method efficiently adds a suffix to a key if it doesn't already
1035 /// end with that suffix.
1036 ///
1037 /// # Arguments
1038 ///
1039 /// * `suffix` - The suffix to ensure is present
1040 ///
1041 /// # Examples
1042 ///
1043 /// ```rust
1044 /// use domain_key::{Key, Domain, KeyDomain};
1045 ///
1046 /// #[derive(Debug)]
1047 /// struct TestDomain;
1048 /// impl Domain for TestDomain {
1049 /// const DOMAIN_NAME: &'static str = "test";
1050 /// }
1051 /// impl KeyDomain for TestDomain {}
1052 /// type TestKey = Key<TestDomain>;
1053 ///
1054 /// let key = TestKey::new("user")?;
1055 /// let suffixed = key.ensure_suffix("_profile")?;
1056 /// assert_eq!(suffixed.as_str(), "user_profile");
1057 ///
1058 /// // If suffix already exists, returns the same key
1059 /// let already_suffixed = suffixed.ensure_suffix("_profile")?;
1060 /// assert_eq!(already_suffixed.as_str(), "user_profile");
1061 /// # Ok::<(), domain_key::KeyParseError>(())
1062 /// ```
1063 /// # Errors
1064 ///
1065 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
1066 pub fn ensure_suffix(&self, suffix: &str) -> Result<Self, KeyParseError> {
1067 if self.ends_with(suffix) {
1068 return Ok(self.clone());
1069 }
1070
1071 let new_len = self.len() + suffix.len();
1072 if new_len > T::MAX_LENGTH {
1073 return Err(KeyParseError::TooLong {
1074 max_length: T::MAX_LENGTH,
1075 actual_length: new_len,
1076 });
1077 }
1078
1079 let result = utils::add_suffix_optimized(&self.inner, suffix);
1080
1081 // Quick validation of suffix only
1082 for (i, c) in suffix.chars().enumerate() {
1083 if !T::allowed_characters(c) {
1084 return Err(KeyParseError::InvalidCharacter {
1085 character: c,
1086 position: self.len() + i,
1087 expected: Some("allowed by domain"),
1088 });
1089 }
1090 }
1091
1092 T::validate_domain_rules(&result).map_err(Self::fix_domain_error)?;
1093
1094 let hash = Self::compute_hash(&result);
1095 let length = new_len.try_into().map_err(|_| KeyParseError::TooLong {
1096 max_length: u32::MAX as usize,
1097 actual_length: new_len,
1098 })?;
1099
1100 Ok(Self {
1101 inner: result,
1102 hash,
1103 length,
1104 _marker: PhantomData,
1105 })
1106 }
1107
1108 /// Get validation rules that this key satisfies
1109 ///
1110 /// Returns detailed information about the validation characteristics
1111 /// of this key and its domain, useful for debugging and introspection.
1112 ///
1113 /// # Examples
1114 ///
1115 /// ```rust
1116 /// use domain_key::{Key, Domain, KeyDomain};
1117 ///
1118 /// #[derive(Debug)]
1119 /// struct TestDomain;
1120 /// impl Domain for TestDomain {
1121 /// const DOMAIN_NAME: &'static str = "test";
1122 /// }
1123 /// impl KeyDomain for TestDomain {
1124 /// const MAX_LENGTH: usize = 32;
1125 /// const HAS_CUSTOM_VALIDATION: bool = true;
1126 /// }
1127 /// type TestKey = Key<TestDomain>;
1128 ///
1129 /// let key = TestKey::new("example")?;
1130 /// let info = key.validation_info();
1131 ///
1132 /// assert_eq!(info.domain_info.name, "test");
1133 /// assert_eq!(info.domain_info.max_length, 32);
1134 /// assert_eq!(info.length, 7);
1135 /// assert!(info.domain_info.has_custom_validation);
1136 /// # Ok::<(), domain_key::KeyParseError>(())
1137 /// ```
1138 #[must_use]
1139 pub fn validation_info(&self) -> KeyValidationInfo {
1140 KeyValidationInfo {
1141 domain_info: crate::domain::domain_info::<T>(),
1142 length: self.len(),
1143 }
1144 }
1145}
1146
1147// ============================================================================
1148// KEY IMPLEMENTATION - HELPER METHODS
1149// ============================================================================
1150
1151impl<T: KeyDomain> Key<T> {
1152 /// Fix domain name in domain validation errors
1153 ///
1154 /// This helper ensures that domain validation errors have the correct
1155 /// domain name, even when they're created generically.
1156 #[inline]
1157 fn fix_domain_error(e: KeyParseError) -> KeyParseError {
1158 match e {
1159 KeyParseError::DomainValidation { message, .. } => KeyParseError::DomainValidation {
1160 domain: T::DOMAIN_NAME,
1161 message,
1162 },
1163 other => other,
1164 }
1165 }
1166
1167 /// Common validation pipeline
1168 ///
1169 /// Performs validation that's common to all domains: length checking,
1170 /// character validation, and structural validation.
1171 ///
1172 /// # Errors
1173 ///
1174 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
1175 pub(crate) fn validate_common(key: &str) -> Result<(), KeyParseError> {
1176 let trimmed = key.trim();
1177
1178 if trimmed.is_empty() {
1179 return Err(KeyParseError::Empty);
1180 }
1181
1182 if trimmed.len() > T::MAX_LENGTH {
1183 return Err(KeyParseError::TooLong {
1184 max_length: T::MAX_LENGTH,
1185 actual_length: trimmed.len(),
1186 });
1187 }
1188
1189 if trimmed.len() < T::min_length() {
1190 return Err(KeyParseError::InvalidStructure {
1191 reason: "key is shorter than minimum required length",
1192 });
1193 }
1194
1195 // Use fast validation
1196 Self::validate_fast(trimmed)
1197 }
1198
1199 /// Fast validation path using optimized algorithms
1200 /// # Errors
1201 ///
1202 /// Returns `KeyParseError` if the prefixed key would be invalid or too long
1203 fn validate_fast(key: &str) -> Result<(), KeyParseError> {
1204 let mut chars = key.char_indices();
1205 let mut prev_char = None;
1206
1207 // Validate first character
1208 if let Some((pos, first)) = chars.next() {
1209 let char_allowed = crate::utils::char_validation::is_key_char_fast(first)
1210 || T::allowed_start_character(first);
1211
1212 if !char_allowed {
1213 return Err(KeyParseError::InvalidCharacter {
1214 character: first,
1215 position: pos,
1216 expected: Some("allowed by domain"),
1217 });
1218 }
1219
1220 prev_char = Some(first);
1221 }
1222
1223 // Validate remaining characters
1224 for (pos, c) in chars {
1225 let char_allowed =
1226 crate::utils::char_validation::is_key_char_fast(c) || T::allowed_characters(c);
1227
1228 if !char_allowed {
1229 return Err(KeyParseError::InvalidCharacter {
1230 character: c,
1231 position: pos,
1232 expected: Some("allowed by domain"),
1233 });
1234 }
1235
1236 if let Some(prev) = prev_char {
1237 if !T::allowed_consecutive_characters(prev, c) {
1238 return Err(KeyParseError::InvalidStructure {
1239 reason: "consecutive characters not allowed",
1240 });
1241 }
1242 }
1243 prev_char = Some(c);
1244 }
1245
1246 // Check last character
1247 if let Some(last) = prev_char {
1248 if !T::allowed_end_character(last) {
1249 return Err(KeyParseError::InvalidStructure {
1250 reason: "invalid end character",
1251 });
1252 }
1253 }
1254
1255 Ok(())
1256 }
1257
1258 /// Normalize a borrowed string
1259 pub(crate) fn normalize(key: &str) -> Cow<'_, str> {
1260 let trimmed = key.trim();
1261
1262 let needs_lowercase =
1263 T::CASE_INSENSITIVE && trimmed.chars().any(|c| c.is_ascii_uppercase());
1264
1265 let base = if needs_lowercase {
1266 Cow::Owned(trimmed.to_ascii_lowercase())
1267 } else {
1268 // Borrow the trimmed slice — no allocation needed
1269 Cow::Borrowed(trimmed)
1270 };
1271
1272 // Apply domain-specific normalization
1273 T::normalize_domain(base)
1274 }
1275
1276 /// Normalize an owned string efficiently
1277 fn normalize_owned(mut key: String) -> String {
1278 // In-place trim: remove leading whitespace by draining, then truncate trailing
1279 let start = key.len() - key.trim_start().len();
1280 if start > 0 {
1281 key.drain(..start);
1282 }
1283 let trimmed_len = key.trim_end().len();
1284 key.truncate(trimmed_len);
1285
1286 if T::CASE_INSENSITIVE {
1287 key.make_ascii_lowercase();
1288 }
1289
1290 // Apply domain normalization
1291 match T::normalize_domain(Cow::Owned(key)) {
1292 Cow::Owned(s) => s,
1293 Cow::Borrowed(_) => unreachable!("We passed Cow::Owned"),
1294 }
1295 }
1296
1297 /// Compute hash using the configured algorithm
1298 ///
1299 /// The hash algorithm is selected at compile time based on feature flags,
1300 /// allowing for different performance/security trade-offs.
1301 pub(crate) fn compute_hash(key: &str) -> u64 {
1302 if key.is_empty() {
1303 return 0;
1304 }
1305
1306 Self::compute_hash_inner(key.as_bytes())
1307 }
1308
1309 /// Inner hash computation dispatched by feature flags
1310 ///
1311 /// Separated to keep each cfg branch as the sole return path,
1312 /// avoiding mixed `return` statements and dead-code warnings.
1313 fn compute_hash_inner(bytes: &[u8]) -> u64 {
1314 // Priority: fast > secure > crypto > default
1315
1316 #[cfg(feature = "fast")]
1317 {
1318 #[cfg(any(
1319 all(target_arch = "x86_64", target_feature = "aes"),
1320 all(
1321 target_arch = "aarch64",
1322 target_feature = "aes",
1323 target_feature = "neon"
1324 )
1325 ))]
1326 {
1327 gxhash::gxhash64(bytes, 0)
1328 }
1329
1330 #[cfg(not(any(
1331 all(target_arch = "x86_64", target_feature = "aes"),
1332 all(
1333 target_arch = "aarch64",
1334 target_feature = "aes",
1335 target_feature = "neon"
1336 )
1337 )))]
1338 {
1339 use core::hash::Hasher;
1340 let mut hasher = ahash::AHasher::default();
1341 hasher.write(bytes);
1342 hasher.finish()
1343 }
1344 }
1345
1346 #[cfg(all(feature = "secure", not(feature = "fast")))]
1347 {
1348 use core::hash::Hasher;
1349 let mut hasher = ahash::AHasher::default();
1350 hasher.write(bytes);
1351 hasher.finish()
1352 }
1353
1354 #[cfg(all(feature = "crypto", not(any(feature = "fast", feature = "secure"))))]
1355 {
1356 let hash = blake3::hash(bytes);
1357 let h = hash.as_bytes();
1358 u64::from_le_bytes([h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]])
1359 }
1360
1361 #[cfg(not(any(feature = "fast", feature = "secure", feature = "crypto")))]
1362 {
1363 #[cfg(feature = "std")]
1364 {
1365 use core::hash::Hasher;
1366 use std::collections::hash_map::DefaultHasher;
1367 let mut hasher = DefaultHasher::new();
1368 hasher.write(bytes);
1369 hasher.finish()
1370 }
1371
1372 #[cfg(not(feature = "std"))]
1373 {
1374 Self::fnv1a_hash(bytes)
1375 }
1376 }
1377 }
1378
1379 /// FNV-1a hash implementation for `no_std` environments
1380 #[expect(
1381 dead_code,
1382 reason = "fallback hash used only when no hash feature is enabled"
1383 )]
1384 fn fnv1a_hash(bytes: &[u8]) -> u64 {
1385 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
1386 const FNV_PRIME: u64 = 0x0100_0000_01b3;
1387
1388 let mut hash = FNV_OFFSET_BASIS;
1389 for &byte in bytes {
1390 hash ^= u64::from(byte);
1391 hash = hash.wrapping_mul(FNV_PRIME);
1392 }
1393 hash
1394 }
1395}
1396
1397// ============================================================================
1398// SUPPORTING TYPES
1399// ============================================================================
1400
1401/// Information about a key's validation characteristics
1402///
1403/// This structure provides detailed information about how a key was validated
1404/// and what domain-specific features are enabled. Domain-level configuration
1405/// is available through the embedded [`DomainInfo`](crate::DomainInfo).
1406#[derive(Debug, Clone, PartialEq, Eq)]
1407pub struct KeyValidationInfo {
1408 /// Full domain configuration
1409 pub domain_info: crate::domain::DomainInfo,
1410 /// Actual length of the key
1411 pub length: usize,
1412}
1413
1414// ============================================================================
1415// STANDARD TRAIT IMPLEMENTATIONS
1416// ============================================================================
1417
1418/// Display implementation shows the key value
1419///
1420/// Outputs just the key string, consistent with `AsRef<str>`, `From<Key<T>> for String`,
1421/// and serde serialization. Use [`Key::domain`] separately when domain context is needed.
1422impl<T: KeyDomain> fmt::Display for Key<T> {
1423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1424 f.write_str(&self.inner)
1425 }
1426}
1427
1428/// `AsRef` implementation for string conversion
1429impl<T: KeyDomain> AsRef<str> for Key<T> {
1430 #[inline]
1431 fn as_ref(&self) -> &str {
1432 &self.inner
1433 }
1434}
1435
1436/// From implementation for converting to String
1437impl<T: KeyDomain> From<Key<T>> for String {
1438 fn from(key: Key<T>) -> Self {
1439 key.inner.into()
1440 }
1441}
1442
1443/// `TryFrom<String>` implementation for owned string conversion
1444///
1445/// This avoids re-borrowing through `&str` when you already have a `String`.
1446impl<T: KeyDomain> TryFrom<String> for Key<T> {
1447 type Error = KeyParseError;
1448
1449 fn try_from(s: String) -> Result<Self, Self::Error> {
1450 Key::from_string(s)
1451 }
1452}
1453
1454/// `TryFrom<&str>` implementation for borrowed string conversion
1455impl<T: KeyDomain> TryFrom<&str> for Key<T> {
1456 type Error = KeyParseError;
1457
1458 fn try_from(s: &str) -> Result<Self, Self::Error> {
1459 Key::new(s)
1460 }
1461}
1462
1463/// `FromStr` implementation for parsing from strings
1464impl<T: KeyDomain> FromStr for Key<T> {
1465 type Err = KeyParseError;
1466
1467 fn from_str(s: &str) -> Result<Self, Self::Err> {
1468 Key::new(s)
1469 }
1470}
1471
1472// ============================================================================
1473// TESTS
1474// ============================================================================
1475
1476#[cfg(test)]
1477mod tests {
1478 use super::*;
1479 use crate::domain::{DefaultDomain, Domain};
1480 #[cfg(not(feature = "std"))]
1481 use alloc::format;
1482 #[cfg(not(feature = "std"))]
1483 use alloc::string::ToString;
1484 #[cfg(not(feature = "std"))]
1485 use alloc::vec;
1486 #[cfg(not(feature = "std"))]
1487 use alloc::vec::Vec;
1488
1489 // Test domain
1490 #[derive(Debug)]
1491 struct TestDomain;
1492
1493 impl Domain for TestDomain {
1494 const DOMAIN_NAME: &'static str = "test";
1495 }
1496
1497 impl KeyDomain for TestDomain {
1498 const MAX_LENGTH: usize = 32;
1499 const HAS_CUSTOM_VALIDATION: bool = true;
1500 const HAS_CUSTOM_NORMALIZATION: bool = true;
1501 const CASE_INSENSITIVE: bool = true;
1502
1503 fn validate_domain_rules(key: &str) -> Result<(), KeyParseError> {
1504 if key.starts_with("invalid_") {
1505 return Err(KeyParseError::domain_error(
1506 Self::DOMAIN_NAME,
1507 "Keys cannot start with 'invalid_'",
1508 ));
1509 }
1510 Ok(())
1511 }
1512
1513 fn normalize_domain(key: Cow<'_, str>) -> Cow<'_, str> {
1514 if key.contains('-') {
1515 Cow::Owned(key.replace('-', "_"))
1516 } else {
1517 key
1518 }
1519 }
1520
1521 fn allowed_characters(c: char) -> bool {
1522 c.is_ascii_alphanumeric() || c == '_' || c == '-'
1523 }
1524
1525 fn validation_help() -> Option<&'static str> {
1526 Some("Use alphanumeric characters, underscores, and hyphens. Cannot start with 'invalid_'.")
1527 }
1528 }
1529
1530 type TestKey = Key<TestDomain>;
1531
1532 #[test]
1533 fn new_key_stores_value_and_domain() {
1534 let key = TestKey::new("valid_key").unwrap();
1535 assert_eq!(key.as_str(), "valid_key");
1536 assert_eq!(key.domain(), "test");
1537 assert_eq!(key.len(), 9);
1538 }
1539
1540 #[test]
1541 fn case_insensitive_domain_lowercases_and_normalizes() {
1542 let key = TestKey::new("Test-Key").unwrap();
1543 assert_eq!(key.as_str(), "test_key");
1544 }
1545
1546 #[test]
1547 fn domain_rules_reject_invalid_prefix() {
1548 let result = TestKey::new("invalid_key");
1549 assert!(result.is_err());
1550
1551 if let Err(KeyParseError::DomainValidation { domain, message }) = result {
1552 assert_eq!(domain, "test");
1553 assert!(message.contains("invalid_"));
1554 } else {
1555 panic!("Expected domain validation error");
1556 }
1557 }
1558
1559 #[test]
1560 fn rejects_empty_too_long_and_invalid_characters() {
1561 // Empty key
1562 assert!(matches!(TestKey::new(""), Err(KeyParseError::Empty)));
1563
1564 // Too long key
1565 let long_key = "a".repeat(50);
1566 assert!(matches!(
1567 TestKey::new(&long_key),
1568 Err(KeyParseError::TooLong {
1569 max_length: 32,
1570 actual_length: 50
1571 })
1572 ));
1573
1574 // Invalid character
1575 let result = TestKey::new("key with spaces");
1576 assert!(matches!(
1577 result,
1578 Err(KeyParseError::InvalidCharacter {
1579 character: ' ',
1580 position: 3,
1581 ..
1582 })
1583 ));
1584 }
1585
1586 #[test]
1587 fn equal_keys_produce_same_hash() {
1588 let key1 = TestKey::new("test_key").unwrap();
1589 let key2 = TestKey::new("test_key").unwrap();
1590
1591 // Same keys should have same hash
1592 assert_eq!(key1.hash(), key2.hash());
1593
1594 let key3 = TestKey::new("different_key").unwrap();
1595 // Different keys should have different hashes (with high probability)
1596 assert_ne!(key1.hash(), key3.hash());
1597 }
1598
1599 #[test]
1600 fn string_query_methods_work_correctly() {
1601 let key = TestKey::new("test_key_example").unwrap();
1602 assert!(key.starts_with("test_"));
1603 assert!(key.ends_with("_example"));
1604 assert!(key.contains("_key_"));
1605 assert_eq!(key.len(), 16);
1606 assert!(!key.is_empty());
1607 }
1608
1609 #[test]
1610 fn from_string_validates_owned_input() {
1611 let key = TestKey::from_string("test_key".to_string()).unwrap();
1612 assert_eq!(key.as_str(), "test_key");
1613 }
1614
1615 #[test]
1616 fn try_from_static_rejects_empty_string() {
1617 let key = TestKey::try_from_static("static_key").unwrap();
1618 assert_eq!(key.as_str(), "static_key");
1619
1620 let invalid = TestKey::try_from_static("");
1621 assert!(invalid.is_err());
1622 }
1623
1624 #[test]
1625 fn validation_info_reflects_domain_config() {
1626 let key = TestKey::new("test_key").unwrap();
1627 let info = key.validation_info();
1628
1629 assert_eq!(info.domain_info.name, "test");
1630 assert_eq!(info.domain_info.max_length, 32);
1631 assert_eq!(info.length, 8);
1632 assert!(info.domain_info.has_custom_validation);
1633 assert!(info.domain_info.has_custom_normalization);
1634 }
1635
1636 #[cfg(feature = "serde")]
1637 #[test]
1638 fn serde_roundtrip_preserves_key() {
1639 let key = TestKey::new("test_key").unwrap();
1640
1641 // Test JSON serialization
1642 let json = serde_json::to_string(&key).unwrap();
1643 assert_eq!(json, r#""test_key""#);
1644
1645 // Test JSON deserialization
1646 let deserialized: TestKey = serde_json::from_str(&json).unwrap();
1647 assert_eq!(deserialized, key);
1648 }
1649
1650 #[test]
1651 fn from_parts_joins_and_splits_roundtrip() {
1652 let key = TestKey::from_parts(&["user", "123", "profile"], "_").unwrap();
1653 assert_eq!(key.as_str(), "user_123_profile");
1654
1655 let parts: Vec<&str> = key.split('_').collect();
1656 assert_eq!(parts, vec!["user", "123", "profile"]);
1657 }
1658
1659 #[test]
1660 fn ensure_prefix_suffix_is_idempotent() {
1661 let key = TestKey::new("profile").unwrap();
1662
1663 let prefixed = key.ensure_prefix("user_").unwrap();
1664 assert_eq!(prefixed.as_str(), "user_profile");
1665
1666 // Already has prefix
1667 let same = prefixed.ensure_prefix("user_").unwrap();
1668 assert_eq!(same.as_str(), "user_profile");
1669
1670 let suffixed = key.ensure_suffix("_v1").unwrap();
1671 assert_eq!(suffixed.as_str(), "profile_v1");
1672
1673 // Already has suffix
1674 let same = suffixed.ensure_suffix("_v1").unwrap();
1675 assert_eq!(same.as_str(), "profile_v1");
1676 }
1677
1678 #[test]
1679 fn display_shows_raw_key_value() {
1680 let key = TestKey::new("example").unwrap();
1681 assert_eq!(format!("{key}"), "example");
1682 }
1683
1684 #[test]
1685 fn into_string_extracts_value() {
1686 let key = TestKey::new("example").unwrap();
1687 let string: String = key.into();
1688 assert_eq!(string, "example");
1689 }
1690
1691 #[test]
1692 fn parse_str_creates_validated_key() {
1693 let key: TestKey = "example".parse().unwrap();
1694 assert_eq!(key.as_str(), "example");
1695 }
1696
1697 #[test]
1698 fn default_domain_accepts_simple_keys() {
1699 type DefaultKey = Key<DefaultDomain>;
1700 let key = DefaultKey::new("test_key").unwrap();
1701 assert_eq!(key.domain(), "default");
1702 assert_eq!(key.as_str(), "test_key");
1703 }
1704
1705 #[test]
1706 fn len_returns_consistent_cached_value() {
1707 let key = TestKey::new("test_key").unwrap();
1708 // Length should be cached and O(1)
1709 assert_eq!(key.len(), 8);
1710 assert_eq!(key.len(), 8); // Second call should use cache
1711 }
1712
1713 #[test]
1714 fn split_methods_produce_same_parts() {
1715 let key = TestKey::new("user_profile_settings").unwrap();
1716
1717 let parts: Vec<&str> = key.split('_').collect();
1718 assert_eq!(parts, vec!["user", "profile", "settings"]);
1719
1720 let cached_parts: Vec<&str> = key.split_cached('_').collect();
1721 assert_eq!(cached_parts, vec!["user", "profile", "settings"]);
1722
1723 let str_parts: Vec<&str> = key.split_str("_").collect();
1724 assert_eq!(str_parts, vec!["user", "profile", "settings"]);
1725 }
1726}