icu_provider/
key.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::error::{DataError, DataErrorKind};
6
7use crate::fallback::{LocaleFallbackConfig, LocaleFallbackPriority, LocaleFallbackSupplement};
8use alloc::borrow::Cow;
9use core::fmt;
10use core::fmt::Write;
11use core::ops::Deref;
12use writeable::{LengthHint, Writeable};
13use zerovec::ule::*;
14
15#[doc(hidden)]
16#[macro_export]
17macro_rules! leading_tag {
18    () => {
19        "\nicu4x_key_tag"
20    };
21}
22
23#[doc(hidden)]
24#[macro_export]
25macro_rules! trailing_tag {
26    () => {
27        "\n"
28    };
29}
30
31#[doc(hidden)]
32#[macro_export]
33macro_rules! tagged {
34    ($without_tags:expr) => {
35        concat!(
36            $crate::leading_tag!(),
37            $without_tags,
38            $crate::trailing_tag!()
39        )
40    };
41}
42
43/// A compact hash of a [`DataKey`]. Useful for keys in maps.
44///
45/// The hash will be stable over time within major releases.
46#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, ULE)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[repr(transparent)]
49pub struct DataKeyHash([u8; 4]);
50
51impl DataKeyHash {
52    const fn compute_from_path(path: DataKeyPath) -> Self {
53        let hash = fxhash_32(
54            path.tagged.as_bytes(),
55            leading_tag!().len(),
56            trailing_tag!().len(),
57        );
58        Self(hash.to_le_bytes())
59    }
60
61    /// Gets the hash value as a byte array.
62    pub const fn to_bytes(self) -> [u8; 4] {
63        self.0
64    }
65}
66
67/// Const function to compute the FxHash of a byte array.
68///
69/// FxHash is a speedy hash algorithm used within rustc. The algorithm is satisfactory for our
70/// use case since the strings being hashed originate from a trusted source (the ICU4X
71/// components), and the hashes are computed at compile time, so we can check for collisions.
72///
73/// We could have considered a SHA or other cryptographic hash function. However, we are using
74/// FxHash because:
75///
76/// 1. There is precedent for this algorithm in Rust
77/// 2. The algorithm is easy to implement as a const function
78/// 3. The amount of code is small enough that we can reasonably keep the algorithm in-tree
79/// 4. FxHash is designed to output 32-bit or 64-bit values, whereas SHA outputs more bits,
80///    such that truncation would be required in order to fit into a u32, partially reducing
81///    the benefit of a cryptographically secure algorithm
82// The indexing operations in this function have been reviewed in detail and won't panic.
83#[allow(clippy::indexing_slicing)]
84const fn fxhash_32(bytes: &[u8], ignore_leading: usize, ignore_trailing: usize) -> u32 {
85    // This code is adapted from https://github.com/rust-lang/rustc-hash,
86    // whose license text is reproduced below.
87    //
88    // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
89    // file at the top-level directory of this distribution and at
90    // http://rust-lang.org/COPYRIGHT.
91    //
92    // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
93    // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
94    // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
95    // option. This file may not be copied, modified, or distributed
96    // except according to those terms.
97
98    if ignore_leading + ignore_trailing >= bytes.len() {
99        return 0;
100    }
101
102    #[inline]
103    const fn hash_word_32(mut hash: u32, word: u32) -> u32 {
104        const ROTATE: u32 = 5;
105        const SEED32: u32 = 0x9e_37_79_b9;
106        hash = hash.rotate_left(ROTATE);
107        hash ^= word;
108        hash = hash.wrapping_mul(SEED32);
109        hash
110    }
111
112    let mut cursor = ignore_leading;
113    let end = bytes.len() - ignore_trailing;
114    let mut hash = 0;
115
116    while end - cursor >= 4 {
117        let word = u32::from_le_bytes([
118            bytes[cursor],
119            bytes[cursor + 1],
120            bytes[cursor + 2],
121            bytes[cursor + 3],
122        ]);
123        hash = hash_word_32(hash, word);
124        cursor += 4;
125    }
126
127    if end - cursor >= 2 {
128        let word = u16::from_le_bytes([bytes[cursor], bytes[cursor + 1]]);
129        hash = hash_word_32(hash, word as u32);
130        cursor += 2;
131    }
132
133    if end - cursor >= 1 {
134        hash = hash_word_32(hash, bytes[cursor] as u32);
135    }
136
137    hash
138}
139
140impl<'a> zerovec::maps::ZeroMapKV<'a> for DataKeyHash {
141    type Container = zerovec::ZeroVec<'a, DataKeyHash>;
142    type Slice = zerovec::ZeroSlice<DataKeyHash>;
143    type GetType = <DataKeyHash as AsULE>::ULE;
144    type OwnedType = DataKeyHash;
145}
146
147impl AsULE for DataKeyHash {
148    type ULE = Self;
149    #[inline]
150    fn to_unaligned(self) -> Self::ULE {
151        self
152    }
153    #[inline]
154    fn from_unaligned(unaligned: Self::ULE) -> Self {
155        unaligned
156    }
157}
158
159// Safe since the ULE type is `self`.
160unsafe impl EqULE for DataKeyHash {}
161
162/// The string path of a data key. For example, "foo@1"
163#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
164pub struct DataKeyPath {
165    // This string literal is wrapped in leading_tag!() and trailing_tag!() to make it detectable
166    // in a compiled binary.
167    tagged: &'static str,
168}
169
170impl DataKeyPath {
171    /// Gets the path as a static string slice.
172    #[inline]
173    pub const fn get(self) -> &'static str {
174        unsafe {
175            // Safe due to invariant that self.path is tagged correctly
176            core::str::from_utf8_unchecked(core::slice::from_raw_parts(
177                self.tagged.as_ptr().add(leading_tag!().len()),
178                self.tagged.len() - trailing_tag!().len() - leading_tag!().len(),
179            ))
180        }
181    }
182}
183
184impl Deref for DataKeyPath {
185    type Target = str;
186    #[inline]
187    fn deref(&self) -> &Self::Target {
188        self.get()
189    }
190}
191
192/// Metadata statically associated with a particular [`DataKey`].
193#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
194#[non_exhaustive]
195pub struct DataKeyMetadata {
196    /// What to prioritize when fallbacking on this [`DataKey`].
197    pub fallback_priority: LocaleFallbackPriority,
198    /// A Unicode extension keyword to consider when loading data for this [`DataKey`].
199    pub extension_key: Option<icu_locid::extensions::unicode::Key>,
200    /// Optional choice for additional fallbacking data required for loading this marker.
201    ///
202    /// For more information, see `LocaleFallbackConfig::fallback_supplement`.
203    pub fallback_supplement: Option<LocaleFallbackSupplement>,
204    /// Whether the key has a singleton value, as opposed to per-locale values. Singleton
205    /// keys behave differently, e.g. they never perform fallback, and can be optimized
206    /// in data providers.
207    pub singleton: bool,
208}
209
210impl DataKeyMetadata {
211    /// Const-friendly version of [`Default::default`].
212    pub const fn const_default() -> Self {
213        Self {
214            fallback_priority: LocaleFallbackPriority::const_default(),
215            extension_key: None,
216            fallback_supplement: None,
217            singleton: false,
218        }
219    }
220
221    #[doc(hidden)]
222    pub const fn construct_internal(
223        fallback_priority: LocaleFallbackPriority,
224        extension_key: Option<icu_locid::extensions::unicode::Key>,
225        fallback_supplement: Option<LocaleFallbackSupplement>,
226        singleton: bool,
227    ) -> Self {
228        Self {
229            fallback_priority,
230            extension_key,
231            fallback_supplement,
232            singleton,
233        }
234    }
235}
236
237impl Default for DataKeyMetadata {
238    #[inline]
239    fn default() -> Self {
240        Self::const_default()
241    }
242}
243
244/// Used for loading data from an ICU4X data provider.
245///
246/// A resource key is tightly coupled with the code that uses it to load data at runtime.
247/// Executables can be searched for `DataKey` instances to produce optimized data files.
248/// Therefore, users should not generally create DataKey instances; they should instead use
249/// the ones exported by a component.
250///
251/// `DataKey`s are created with the [`data_key!`](crate::data_key) macro:
252///
253/// ```
254/// # use icu_provider::DataKey;
255/// const K: DataKey = icu_provider::data_key!("foo/bar@1");
256/// ```
257///
258/// The human-readable path string ends with `@` followed by one or more digits (the version
259/// number). Paths do not contain characters other than ASCII letters and digits, `_`, `/`.
260///
261/// Invalid paths are compile-time errors (as [`data_key!`](crate::data_key) uses `const`).
262///
263/// ```compile_fail,E0080
264/// # use icu_provider::DataKey;
265/// const K: DataKey = icu_provider::data_key!("foo/../bar@1");
266/// ```
267#[derive(Copy, Clone)]
268pub struct DataKey {
269    path: DataKeyPath,
270    hash: DataKeyHash,
271    metadata: DataKeyMetadata,
272}
273
274impl PartialEq for DataKey {
275    #[inline]
276    fn eq(&self, other: &Self) -> bool {
277        self.hash == other.hash && self.path == other.path && self.metadata == other.metadata
278    }
279}
280
281impl Eq for DataKey {}
282
283impl Ord for DataKey {
284    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
285        self.path
286            .cmp(&other.path)
287            .then_with(|| self.metadata.cmp(&other.metadata))
288    }
289}
290
291impl PartialOrd for DataKey {
292    #[inline]
293    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
294        Some(self.cmp(other))
295    }
296}
297
298impl core::hash::Hash for DataKey {
299    #[inline]
300    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
301        self.hash.hash(state)
302    }
303}
304
305impl DataKey {
306    /// Gets a human-readable representation of a [`DataKey`].
307    ///
308    /// The human-readable path string ends with `@` followed by one or more digits (the version
309    /// number). Paths do not contain characters other than ASCII letters and digits, `_`, `/`.
310    ///
311    /// Useful for reading and writing data to a file system.
312    #[inline]
313    pub const fn path(self) -> DataKeyPath {
314        self.path
315    }
316
317    /// Gets a platform-independent hash of a [`DataKey`].
318    ///
319    /// The hash is 4 bytes and allows for fast key comparison.
320    ///
321    /// # Example
322    ///
323    /// ```
324    /// use icu_provider::DataKey;
325    /// use icu_provider::DataKeyHash;
326    ///
327    /// const KEY: DataKey = icu_provider::data_key!("foo@1");
328    /// const KEY_HASH: DataKeyHash = KEY.hashed();
329    ///
330    /// assert_eq!(KEY_HASH.to_bytes(), [0xe2, 0xb6, 0x17, 0x71]);
331    /// ```
332    #[inline]
333    pub const fn hashed(self) -> DataKeyHash {
334        self.hash
335    }
336
337    /// Gets the metadata associated with this [`DataKey`].
338    #[inline]
339    pub const fn metadata(self) -> DataKeyMetadata {
340        self.metadata
341    }
342
343    /// Returns the [`LocaleFallbackConfig`] for this [`DataKey`].
344    #[inline]
345    pub const fn fallback_config(self) -> LocaleFallbackConfig {
346        let mut config = LocaleFallbackConfig::const_default();
347        config.priority = self.metadata.fallback_priority;
348        config.extension_key = self.metadata.extension_key;
349        config.fallback_supplement = self.metadata.fallback_supplement;
350        config
351    }
352
353    /// Constructs a [`DataKey`] from a path and metadata.
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use icu_provider::data_key;
359    /// use icu_provider::DataKey;
360    ///
361    /// const CONST_KEY: DataKey = data_key!("foo@1");
362    ///
363    /// let runtime_key =
364    ///     DataKey::from_path_and_metadata(CONST_KEY.path(), CONST_KEY.metadata());
365    ///
366    /// assert_eq!(CONST_KEY, runtime_key);
367    /// ```
368    #[inline]
369    pub const fn from_path_and_metadata(path: DataKeyPath, metadata: DataKeyMetadata) -> Self {
370        Self {
371            path,
372            hash: DataKeyHash::compute_from_path(path),
373            metadata,
374        }
375    }
376
377    #[doc(hidden)]
378    // Error is a str of the expected character class and the index where it wasn't encountered
379    // The indexing operations in this function have been reviewed in detail and won't panic.
380    #[allow(clippy::indexing_slicing)]
381    pub const fn construct_internal(
382        path: &'static str,
383        metadata: DataKeyMetadata,
384    ) -> Result<Self, (&'static str, usize)> {
385        if path.len() < leading_tag!().len() + trailing_tag!().len() {
386            return Err(("tag", 0));
387        }
388        // Start and end of the untagged part
389        let start = leading_tag!().len();
390        let end = path.len() - trailing_tag!().len();
391
392        // Check tags
393        let mut i = 0;
394        while i < leading_tag!().len() {
395            if path.as_bytes()[i] != leading_tag!().as_bytes()[i] {
396                return Err(("tag", 0));
397            }
398            i += 1;
399        }
400        i = 0;
401        while i < trailing_tag!().len() {
402            if path.as_bytes()[end + i] != trailing_tag!().as_bytes()[i] {
403                return Err(("tag", end + 1));
404            }
405            i += 1;
406        }
407
408        match Self::validate_path_manual_slice(path, start, end) {
409            Ok(()) => (),
410            Err(e) => return Err(e),
411        };
412
413        let path = DataKeyPath { tagged: path };
414
415        Ok(Self {
416            path,
417            hash: DataKeyHash::compute_from_path(path),
418            metadata,
419        })
420    }
421
422    const fn validate_path_manual_slice(
423        path: &'static str,
424        start: usize,
425        end: usize,
426    ) -> Result<(), (&'static str, usize)> {
427        debug_assert!(start <= end);
428        debug_assert!(end <= path.len());
429        // Regex: [a-zA-Z0-9_][a-zA-Z0-9_/]*@[0-9]+
430        enum State {
431            Empty,
432            Body,
433            At,
434            Version,
435        }
436        use State::*;
437        let mut i = start;
438        let mut state = Empty;
439        loop {
440            let byte = if i < end {
441                #[allow(clippy::indexing_slicing)] // protected by debug assertion
442                Some(path.as_bytes()[i])
443            } else {
444                None
445            };
446            state = match (state, byte) {
447                (Empty | Body, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) => Body,
448                (Body, Some(b'/')) => Body,
449                (Body, Some(b'@')) => At,
450                (At | Version, Some(b'0'..=b'9')) => Version,
451                // One of these cases will be hit at the latest when i == end, so the loop converges.
452                (Version, None) => {
453                    return Ok(());
454                }
455
456                (Empty, _) => return Err(("[a-zA-Z0-9_]", i)),
457                (Body, _) => return Err(("[a-zA-z0-9_/@]", i)),
458                (At, _) => return Err(("[0-9]", i)),
459                (Version, _) => return Err(("[0-9]", i)),
460            };
461            i += 1;
462        }
463    }
464
465    /// Returns [`Ok`] if this data key matches the argument, or the appropriate error.
466    ///
467    /// Convenience method for data providers that support a single [`DataKey`].
468    ///
469    /// # Examples
470    ///
471    /// ```
472    /// use icu_provider::prelude::*;
473    ///
474    /// const FOO_BAR: DataKey = icu_provider::data_key!("foo/bar@1");
475    /// const FOO_BAZ: DataKey = icu_provider::data_key!("foo/baz@1");
476    /// const BAR_BAZ: DataKey = icu_provider::data_key!("bar/baz@1");
477    ///
478    /// assert!(matches!(FOO_BAR.match_key(FOO_BAR), Ok(())));
479    /// assert!(matches!(
480    ///     FOO_BAR.match_key(FOO_BAZ),
481    ///     Err(DataError {
482    ///         kind: DataErrorKind::MissingDataKey,
483    ///         ..
484    ///     })
485    /// ));
486    /// assert!(matches!(
487    ///     FOO_BAR.match_key(BAR_BAZ),
488    ///     Err(DataError {
489    ///         kind: DataErrorKind::MissingDataKey,
490    ///         ..
491    ///     })
492    /// ));
493    ///
494    /// // The error context contains the argument:
495    /// assert_eq!(FOO_BAR.match_key(BAR_BAZ).unwrap_err().key, Some(BAR_BAZ));
496    /// ```
497    pub fn match_key(self, key: Self) -> Result<(), DataError> {
498        if self == key {
499            Ok(())
500        } else {
501            Err(DataErrorKind::MissingDataKey.with_key(key))
502        }
503    }
504}
505
506/// See [`DataKey`].
507#[macro_export]
508macro_rules! data_key {
509    ($path:expr) => {{
510        $crate::data_key!($path, $crate::DataKeyMetadata::const_default())
511    }};
512    ($path:expr, $metadata:expr) => {{
513        // Force the DataKey into a const context
514        const RESOURCE_KEY_MACRO_CONST: $crate::DataKey = {
515            match $crate::DataKey::construct_internal($crate::tagged!($path), $metadata) {
516                Ok(v) => v,
517                #[allow(clippy::panic)] // Const context
518                Err(_) => panic!(concat!("Invalid resource key: ", $path)),
519                // TODO Once formatting is const:
520                // Err((expected, index)) => panic!(
521                //     "Invalid resource key {:?}: expected {:?}, found {:?} ",
522                //     $path,
523                //     expected,
524                //     $crate::tagged!($path).get(index..))
525                // );
526            }
527        };
528        RESOURCE_KEY_MACRO_CONST
529    }};
530}
531
532impl fmt::Debug for DataKey {
533    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
534        f.write_str("DataKey{")?;
535        fmt::Display::fmt(self, f)?;
536        f.write_char('}')?;
537        Ok(())
538    }
539}
540
541impl Writeable for DataKey {
542    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
543        self.path().write_to(sink)
544    }
545
546    fn writeable_length_hint(&self) -> LengthHint {
547        self.path().writeable_length_hint()
548    }
549
550    fn write_to_string(&self) -> Cow<str> {
551        Cow::Borrowed(self.path().get())
552    }
553}
554
555writeable::impl_display_with_writeable!(DataKey);
556
557#[test]
558fn test_path_syntax() {
559    // Valid keys:
560    DataKey::construct_internal(tagged!("hello/world@1"), Default::default()).unwrap();
561    DataKey::construct_internal(tagged!("hello/world/foo@1"), Default::default()).unwrap();
562    DataKey::construct_internal(tagged!("hello/world@999"), Default::default()).unwrap();
563    DataKey::construct_internal(tagged!("hello_world/foo@1"), Default::default()).unwrap();
564    DataKey::construct_internal(tagged!("hello_458/world@1"), Default::default()).unwrap();
565    DataKey::construct_internal(tagged!("hello_world@1"), Default::default()).unwrap();
566
567    // No version:
568    assert_eq!(
569        DataKey::construct_internal(tagged!("hello/world"), Default::default()),
570        Err((
571            "[a-zA-z0-9_/@]",
572            concat!(leading_tag!(), "hello/world").len()
573        ))
574    );
575
576    assert_eq!(
577        DataKey::construct_internal(tagged!("hello/world@"), Default::default()),
578        Err(("[0-9]", concat!(leading_tag!(), "hello/world@").len()))
579    );
580    assert_eq!(
581        DataKey::construct_internal(tagged!("hello/world@foo"), Default::default()),
582        Err(("[0-9]", concat!(leading_tag!(), "hello/world@").len()))
583    );
584    assert_eq!(
585        DataKey::construct_internal(tagged!("hello/world@1foo"), Default::default()),
586        Err(("[0-9]", concat!(leading_tag!(), "hello/world@1").len()))
587    );
588
589    // Meta no longer accepted:
590    assert_eq!(
591        DataKey::construct_internal(tagged!("foo@1[R]"), Default::default()),
592        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
593    );
594    assert_eq!(
595        DataKey::construct_internal(tagged!("foo@1[u-ca]"), Default::default()),
596        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
597    );
598    assert_eq!(
599        DataKey::construct_internal(tagged!("foo@1[R][u-ca]"), Default::default()),
600        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
601    );
602
603    // Invalid meta:
604    assert_eq!(
605        DataKey::construct_internal(tagged!("foo@1[U]"), Default::default()),
606        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
607    );
608    assert_eq!(
609        DataKey::construct_internal(tagged!("foo@1[uca]"), Default::default()),
610        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
611    );
612    assert_eq!(
613        DataKey::construct_internal(tagged!("foo@1[u-"), Default::default()),
614        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
615    );
616    assert_eq!(
617        DataKey::construct_internal(tagged!("foo@1[u-caa]"), Default::default()),
618        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
619    );
620    assert_eq!(
621        DataKey::construct_internal(tagged!("foo@1[R"), Default::default()),
622        Err(("[0-9]", concat!(leading_tag!(), "foo@1").len()))
623    );
624
625    // Invalid characters:
626    assert_eq!(
627        DataKey::construct_internal(tagged!("你好/世界@1"), Default::default()),
628        Err(("[a-zA-Z0-9_]", leading_tag!().len()))
629    );
630
631    // Invalid tag:
632    assert_eq!(
633        DataKey::construct_internal(
634            concat!("hello/world@1", trailing_tag!()),
635            Default::default()
636        ),
637        Err(("tag", 0))
638    );
639    assert_eq!(
640        DataKey::construct_internal(concat!(leading_tag!(), "hello/world@1"), Default::default()),
641        Err(("tag", concat!(leading_tag!(), "hello/world@1").len()))
642    );
643    assert_eq!(
644        DataKey::construct_internal("hello/world@1", Default::default()),
645        Err(("tag", 0))
646    );
647}
648
649#[test]
650fn test_key_to_string() {
651    struct KeyTestCase {
652        pub key: DataKey,
653        pub expected: &'static str,
654    }
655
656    for cas in [
657        KeyTestCase {
658            key: data_key!("core/cardinal@1"),
659            expected: "core/cardinal@1",
660        },
661        KeyTestCase {
662            key: data_key!("core/maxlengthsubcatg@1"),
663            expected: "core/maxlengthsubcatg@1",
664        },
665        KeyTestCase {
666            key: data_key!("core/cardinal@65535"),
667            expected: "core/cardinal@65535",
668        },
669    ] {
670        writeable::assert_writeable_eq!(&cas.key, cas.expected);
671        assert_eq!(cas.expected, &*cas.key.path());
672    }
673}
674
675#[test]
676fn test_hash_word_32() {
677    assert_eq!(0, fxhash_32(b"", 0, 0));
678    assert_eq!(0, fxhash_32(b"a", 1, 0));
679    assert_eq!(0, fxhash_32(b"a", 0, 1));
680    assert_eq!(0, fxhash_32(b"a", 0, 10));
681    assert_eq!(0, fxhash_32(b"a", 10, 0));
682    assert_eq!(0, fxhash_32(b"a", 1, 1));
683    assert_eq!(0xF3051F19, fxhash_32(b"a", 0, 0));
684    assert_eq!(0x2F9DF119, fxhash_32(b"ab", 0, 0));
685    assert_eq!(0xCB1D9396, fxhash_32(b"abc", 0, 0));
686    assert_eq!(0x8628F119, fxhash_32(b"abcd", 0, 0));
687    assert_eq!(0xBEBDB56D, fxhash_32(b"abcde", 0, 0));
688    assert_eq!(0x1CE8476D, fxhash_32(b"abcdef", 0, 0));
689    assert_eq!(0xC0F176A4, fxhash_32(b"abcdefg", 0, 0));
690    assert_eq!(0x09AB476D, fxhash_32(b"abcdefgh", 0, 0));
691    assert_eq!(0xB72F5D88, fxhash_32(b"abcdefghi", 0, 0));
692}
693
694#[test]
695fn test_key_hash() {
696    struct KeyTestCase {
697        pub key: DataKey,
698        pub hash: DataKeyHash,
699    }
700
701    for cas in [
702        KeyTestCase {
703            key: data_key!("core/cardinal@1"),
704            hash: DataKeyHash([172, 207, 42, 236]),
705        },
706        KeyTestCase {
707            key: data_key!("core/maxlengthsubcatg@1"),
708            hash: DataKeyHash([193, 6, 79, 61]),
709        },
710        KeyTestCase {
711            key: data_key!("core/cardinal@65535"),
712            hash: DataKeyHash([176, 131, 182, 223]),
713        },
714    ] {
715        assert_eq!(cas.hash, cas.key.hashed(), "{}", cas.key);
716    }
717}