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!`] 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 #[expect(
1383 dead_code,
1384 reason = "fallback hash used only when no hash feature is enabled"
1385 )]
1386 fn fnv1a_hash(bytes: &[u8]) -> u64 {
1387 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
1388 const FNV_PRIME: u64 = 0x0100_0000_01b3;
1389
1390 let mut hash = FNV_OFFSET_BASIS;
1391 for &byte in bytes {
1392 hash ^= u64::from(byte);
1393 hash = hash.wrapping_mul(FNV_PRIME);
1394 }
1395 hash
1396 }
1397}
1398
1399// ============================================================================
1400// SUPPORTING TYPES
1401// ============================================================================
1402
1403/// Information about a key's validation characteristics
1404///
1405/// This structure provides detailed information about how a key was validated
1406/// and what domain-specific features are enabled. Domain-level configuration
1407/// is available through the embedded [`DomainInfo`](crate::DomainInfo).
1408#[derive(Debug, Clone, PartialEq, Eq)]
1409pub struct KeyValidationInfo {
1410 /// Full domain configuration
1411 pub domain_info: crate::domain::DomainInfo,
1412 /// Actual length of the key
1413 pub length: usize,
1414}
1415
1416// ============================================================================
1417// STANDARD TRAIT IMPLEMENTATIONS
1418// ============================================================================
1419
1420/// Display implementation shows the key value
1421///
1422/// Outputs just the key string, consistent with `AsRef<str>`, `From<Key<T>> for String`,
1423/// and serde serialization. Use [`Key::domain`] separately when domain context is needed.
1424impl<T: KeyDomain> fmt::Display for Key<T> {
1425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1426 f.write_str(&self.inner)
1427 }
1428}
1429
1430/// `Deref` implementation for automatic coercion to `&str`
1431///
1432/// This allows `&key` to automatically coerce to `&str` in contexts
1433/// that expect a string slice, eliminating the need for explicit
1434/// `.as_ref()` or `.as_str()` calls in most situations.
1435///
1436/// # Examples
1437///
1438/// ```rust
1439/// use domain_key::{Key, Domain, KeyDomain};
1440///
1441/// #[derive(Debug)]
1442/// struct TestDomain;
1443/// impl Domain for TestDomain {
1444/// const DOMAIN_NAME: &'static str = "test";
1445/// }
1446/// impl KeyDomain for TestDomain {}
1447/// type TestKey = Key<TestDomain>;
1448///
1449/// let key = TestKey::new("example")?;
1450///
1451/// // Automatic coercion — no .as_ref() needed
1452/// let s: &str = &key;
1453/// assert_eq!(s, "example");
1454///
1455/// // Works with functions expecting &str
1456/// fn takes_str(_s: &str) {}
1457/// takes_str(&key);
1458/// # Ok::<(), domain_key::KeyParseError>(())
1459/// ```
1460impl<T: KeyDomain> Deref for Key<T> {
1461 type Target = str;
1462
1463 #[inline]
1464 fn deref(&self) -> &str {
1465 &self.inner
1466 }
1467}
1468
1469/// `AsRef` implementation for string conversion
1470impl<T: KeyDomain> AsRef<str> for Key<T> {
1471 #[inline]
1472 fn as_ref(&self) -> &str {
1473 self
1474 }
1475}
1476
1477/// `Borrow<str>` implementation enabling `HashMap<Key<T>, V>::get("str")`
1478///
1479/// This is sound because the [`Hash`] trait implementation for `Key<T>`
1480/// delegates to `str`'s hash, satisfying the contract
1481/// `hash(key) == hash(key.borrow())`.
1482impl<T: KeyDomain> Borrow<str> for Key<T> {
1483 #[inline]
1484 fn borrow(&self) -> &str {
1485 self
1486 }
1487}
1488
1489/// From implementation for converting to String
1490impl<T: KeyDomain> From<Key<T>> for String {
1491 fn from(key: Key<T>) -> Self {
1492 key.inner.into()
1493 }
1494}
1495
1496/// Creates a `Key` from a pre-validated [`SmartString`] **without re-validation**.
1497///
1498/// This is intended for internal or advanced usage where the caller has
1499/// already ensured that the string satisfies all domain rules (length,
1500/// allowed characters, normalization, etc.). Hash is computed
1501/// automatically, but **no validation or normalization is performed**.
1502///
1503/// # Safety (logical)
1504///
1505/// If the string does not satisfy the domain's invariants the resulting
1506/// key will silently violate those invariants. Prefer [`Key::new`] or
1507/// [`Key::from_string`] unless you are certain the input is valid.
1508impl<T: KeyDomain> From<SmartString> for Key<T> {
1509 #[inline]
1510 fn from(inner: SmartString) -> Self {
1511 let hash = Self::compute_hash(&inner);
1512
1513 Self {
1514 inner,
1515 hash,
1516 _marker: PhantomData,
1517 }
1518 }
1519}
1520
1521/// `TryFrom<String>` implementation for owned string conversion
1522///
1523/// This avoids re-borrowing through `&str` when you already have a `String`.
1524impl<T: KeyDomain> TryFrom<String> for Key<T> {
1525 type Error = KeyParseError;
1526
1527 fn try_from(s: String) -> Result<Self, Self::Error> {
1528 Key::from_string(s)
1529 }
1530}
1531
1532/// `TryFrom<&str>` implementation for borrowed string conversion
1533impl<T: KeyDomain> TryFrom<&str> for Key<T> {
1534 type Error = KeyParseError;
1535
1536 fn try_from(s: &str) -> Result<Self, Self::Error> {
1537 Key::new(s)
1538 }
1539}
1540
1541/// `FromStr` implementation for parsing from strings
1542impl<T: KeyDomain> FromStr for Key<T> {
1543 type Err = KeyParseError;
1544
1545 fn from_str(s: &str) -> Result<Self, Self::Err> {
1546 Key::new(s)
1547 }
1548}
1549
1550// ============================================================================
1551// TESTS
1552// ============================================================================
1553
1554#[cfg(test)]
1555mod tests {
1556 use super::*;
1557 use crate::domain::{DefaultDomain, Domain};
1558 #[cfg(not(feature = "std"))]
1559 use alloc::format;
1560 #[cfg(not(feature = "std"))]
1561 use alloc::string::ToString;
1562 #[cfg(not(feature = "std"))]
1563 use alloc::vec;
1564 #[cfg(not(feature = "std"))]
1565 use alloc::vec::Vec;
1566
1567 // Test domain
1568 #[derive(Debug)]
1569 struct TestDomain;
1570
1571 impl Domain for TestDomain {
1572 const DOMAIN_NAME: &'static str = "test";
1573 }
1574
1575 impl KeyDomain for TestDomain {
1576 const MAX_LENGTH: usize = 32;
1577 const HAS_CUSTOM_VALIDATION: bool = true;
1578 const HAS_CUSTOM_NORMALIZATION: bool = true;
1579 const CASE_INSENSITIVE: bool = true;
1580
1581 fn validate_domain_rules(key: &str) -> Result<(), KeyParseError> {
1582 if key.starts_with("invalid_") {
1583 return Err(KeyParseError::domain_error(
1584 Self::DOMAIN_NAME,
1585 "Keys cannot start with 'invalid_'",
1586 ));
1587 }
1588 Ok(())
1589 }
1590
1591 fn normalize_domain(key: Cow<'_, str>) -> Cow<'_, str> {
1592 if key.contains('-') {
1593 Cow::Owned(key.replace('-', "_"))
1594 } else {
1595 key
1596 }
1597 }
1598
1599 fn allowed_characters(c: char) -> bool {
1600 c.is_ascii_alphanumeric() || c == '_' || c == '-'
1601 }
1602
1603 fn validation_help() -> Option<&'static str> {
1604 Some("Use alphanumeric characters, underscores, and hyphens. Cannot start with 'invalid_'.")
1605 }
1606 }
1607
1608 type TestKey = Key<TestDomain>;
1609
1610 #[test]
1611 fn new_key_stores_value_and_domain() {
1612 let key = TestKey::new("valid_key").unwrap();
1613 assert_eq!(key.as_str(), "valid_key");
1614 assert_eq!(key.domain(), "test");
1615 assert_eq!(key.len(), 9);
1616 }
1617
1618 #[test]
1619 fn case_insensitive_domain_lowercases_and_normalizes() {
1620 let key = TestKey::new("Test-Key").unwrap();
1621 assert_eq!(key.as_str(), "test_key");
1622 }
1623
1624 #[test]
1625 fn domain_rules_reject_invalid_prefix() {
1626 let result = TestKey::new("invalid_key");
1627 assert!(result.is_err());
1628
1629 if let Err(KeyParseError::DomainValidation { domain, message }) = result {
1630 assert_eq!(domain, "test");
1631 assert!(message.contains("invalid_"));
1632 } else {
1633 panic!("Expected domain validation error");
1634 }
1635 }
1636
1637 #[test]
1638 fn rejects_empty_too_long_and_invalid_characters() {
1639 // Empty key
1640 assert!(matches!(TestKey::new(""), Err(KeyParseError::Empty)));
1641
1642 // Too long key
1643 let long_key = "a".repeat(50);
1644 assert!(matches!(
1645 TestKey::new(&long_key),
1646 Err(KeyParseError::TooLong {
1647 max_length: 32,
1648 actual_length: 50
1649 })
1650 ));
1651
1652 // Invalid character
1653 let result = TestKey::new("key with spaces");
1654 assert!(matches!(
1655 result,
1656 Err(KeyParseError::InvalidCharacter {
1657 character: ' ',
1658 position: 3,
1659 ..
1660 })
1661 ));
1662 }
1663
1664 #[test]
1665 fn equal_keys_produce_same_hash() {
1666 use core::hash::{Hash, Hasher};
1667
1668 let key1 = TestKey::new("test_key").unwrap();
1669 let key2 = TestKey::new("test_key").unwrap();
1670
1671 // Pre-computed hashes should match
1672 assert_eq!(key1.hash(), key2.hash());
1673
1674 let key3 = TestKey::new("different_key").unwrap();
1675 assert_ne!(key1.hash(), key3.hash());
1676
1677 // Hash trait should produce same result as hashing the raw &str,
1678 // so Borrow<str> contract is upheld.
1679 #[cfg(feature = "std")]
1680 {
1681 let mut h = std::collections::hash_map::DefaultHasher::new();
1682 Hash::hash(&key1, &mut h);
1683 let key_trait_hash = h.finish();
1684 let mut h = std::collections::hash_map::DefaultHasher::new();
1685 Hash::hash(key1.as_str(), &mut h);
1686 let str_trait_hash = h.finish();
1687 assert_eq!(key_trait_hash, str_trait_hash);
1688 }
1689 }
1690
1691 #[test]
1692 fn string_query_methods_work_correctly() {
1693 let key = TestKey::new("test_key_example").unwrap();
1694 assert!(key.starts_with("test_"));
1695 assert!(key.ends_with("_example"));
1696 assert!(key.contains("_key_"));
1697 assert_eq!(key.len(), 16);
1698 assert!(!key.is_empty());
1699 }
1700
1701 #[test]
1702 fn from_string_validates_owned_input() {
1703 let key = TestKey::from_string("test_key".to_string()).unwrap();
1704 assert_eq!(key.as_str(), "test_key");
1705 }
1706
1707 #[test]
1708 fn try_from_static_rejects_empty_string() {
1709 let key = TestKey::try_from_static("static_key").unwrap();
1710 assert_eq!(key.as_str(), "static_key");
1711
1712 let invalid = TestKey::try_from_static("");
1713 assert!(invalid.is_err());
1714 }
1715
1716 #[test]
1717 fn validation_info_reflects_domain_config() {
1718 let key = TestKey::new("test_key").unwrap();
1719 let info = key.validation_info();
1720
1721 assert_eq!(info.domain_info.name, "test");
1722 assert_eq!(info.domain_info.max_length, 32);
1723 assert_eq!(info.length, 8);
1724 assert!(info.domain_info.has_custom_validation);
1725 assert!(info.domain_info.has_custom_normalization);
1726 }
1727
1728 #[cfg(feature = "serde")]
1729 #[test]
1730 fn serde_roundtrip_preserves_key() {
1731 let key = TestKey::new("test_key").unwrap();
1732
1733 // Test JSON serialization
1734 let json = serde_json::to_string(&key).unwrap();
1735 assert_eq!(json, r#""test_key""#);
1736
1737 // Test JSON deserialization
1738 let deserialized: TestKey = serde_json::from_str(&json).unwrap();
1739 assert_eq!(deserialized, key);
1740 }
1741
1742 #[test]
1743 fn from_parts_joins_and_splits_roundtrip() {
1744 let key = TestKey::from_parts(&["user", "123", "profile"], "_").unwrap();
1745 assert_eq!(key.as_str(), "user_123_profile");
1746
1747 let parts: Vec<&str> = key.split('_').collect();
1748 assert_eq!(parts, vec!["user", "123", "profile"]);
1749 }
1750
1751 #[test]
1752 fn ensure_prefix_suffix_is_idempotent() {
1753 let key = TestKey::new("profile").unwrap();
1754
1755 let prefixed = key.ensure_prefix("user_").unwrap();
1756 assert_eq!(prefixed.as_str(), "user_profile");
1757
1758 // Already has prefix
1759 let same = prefixed.ensure_prefix("user_").unwrap();
1760 assert_eq!(same.as_str(), "user_profile");
1761
1762 let suffixed = key.ensure_suffix("_v1").unwrap();
1763 assert_eq!(suffixed.as_str(), "profile_v1");
1764
1765 // Already has suffix
1766 let same = suffixed.ensure_suffix("_v1").unwrap();
1767 assert_eq!(same.as_str(), "profile_v1");
1768 }
1769
1770 #[test]
1771 fn display_shows_raw_key_value() {
1772 let key = TestKey::new("example").unwrap();
1773 assert_eq!(format!("{key}"), "example");
1774 }
1775
1776 #[test]
1777 fn into_string_extracts_value() {
1778 let key = TestKey::new("example").unwrap();
1779 let string: String = key.into();
1780 assert_eq!(string, "example");
1781 }
1782
1783 #[test]
1784 fn parse_str_creates_validated_key() {
1785 let key: TestKey = "example".parse().unwrap();
1786 assert_eq!(key.as_str(), "example");
1787 }
1788
1789 #[test]
1790 fn default_domain_accepts_simple_keys() {
1791 type DefaultKey = Key<DefaultDomain>;
1792 let key = DefaultKey::new("test_key").unwrap();
1793 assert_eq!(key.domain(), "default");
1794 assert_eq!(key.as_str(), "test_key");
1795 }
1796
1797 #[test]
1798 fn len_returns_consistent_cached_value() {
1799 let key = TestKey::new("test_key").unwrap();
1800 assert_eq!(key.len(), 8);
1801 assert_eq!(key.len(), 8); // Second call — same result
1802 }
1803
1804 #[test]
1805 fn split_methods_produce_same_parts() {
1806 let key = TestKey::new("user_profile_settings").unwrap();
1807
1808 let parts: Vec<&str> = key.split('_').collect();
1809 assert_eq!(parts, vec!["user", "profile", "settings"]);
1810
1811 let cached_parts: Vec<&str> = key.split_cached('_').collect();
1812 assert_eq!(cached_parts, vec!["user", "profile", "settings"]);
1813
1814 let str_parts: Vec<&str> = key.split_str("_").collect();
1815 assert_eq!(str_parts, vec!["user", "profile", "settings"]);
1816 }
1817
1818 #[test]
1819 fn deref_coerces_to_str() {
1820 fn takes_str(s: &str) -> &str {
1821 s
1822 }
1823 let key = TestKey::new("hello").unwrap();
1824 // Deref allows &Key<T> → &str coercion
1825 let s: &str = &key;
1826 assert_eq!(s, "hello");
1827
1828 // Works with functions that accept &str
1829 assert_eq!(takes_str(&key), "hello");
1830 }
1831
1832 #[test]
1833 fn from_smartstring_creates_key_without_revalidation() {
1834 use smartstring::alias::String as SmartString;
1835
1836 let smart = SmartString::from("pre_validated");
1837 let key: TestKey = TestKey::from(smart);
1838 assert_eq!(key.as_str(), "pre_validated");
1839 assert_eq!(key.len(), 13);
1840 // Hash should be computed correctly
1841 assert_ne!(key.hash(), 0);
1842 }
1843
1844 #[cfg(feature = "std")]
1845 #[test]
1846 fn borrow_str_enables_hashmap_get_by_str() {
1847 use std::collections::HashMap;
1848
1849 let mut map: HashMap<TestKey, u32> = HashMap::new();
1850 let key = TestKey::new("lookup_test").unwrap();
1851 map.insert(key, 42);
1852
1853 // Lookup by &str — works thanks to Borrow<str>
1854 assert_eq!(map.get("lookup_test"), Some(&42));
1855 assert_eq!(map.get("nonexistent"), None);
1856 }
1857
1858 #[test]
1859 fn struct_is_32_bytes() {
1860 // SmartString(24) + u64 hash(8) + PhantomData(0) = 32 bytes
1861 assert_eq!(core::mem::size_of::<TestKey>(), 32);
1862 }
1863}