Skip to main content

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