Coverage Report

Created: 2025-06-16 06:50

/rust/registry/src/index.crates.io-6f17d22bba15001f/icu_provider-1.5.0/src/key.rs
Line
Count
Source (jump to first uncovered line)
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
5
use crate::error::{DataError, DataErrorKind};
6
7
use crate::fallback::{LocaleFallbackConfig, LocaleFallbackPriority, LocaleFallbackSupplement};
8
use alloc::borrow::Cow;
9
use core::fmt;
10
use core::fmt::Write;
11
use core::ops::Deref;
12
use writeable::{LengthHint, Writeable};
13
use zerovec::ule::*;
14
15
#[doc(hidden)]
16
#[macro_export]
17
macro_rules! leading_tag {
18
    () => {
19
        "\nicu4x_key_tag"
20
    };
21
}
22
23
#[doc(hidden)]
24
#[macro_export]
25
macro_rules! trailing_tag {
26
    () => {
27
        "\n"
28
    };
29
}
30
31
#[doc(hidden)]
32
#[macro_export]
33
macro_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
0
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, ULE)]
47
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48
#[repr(transparent)]
49
pub struct DataKeyHash([u8; 4]);
50
51
impl DataKeyHash {
52
0
    const fn compute_from_path(path: DataKeyPath) -> Self {
53
0
        let hash = fxhash_32(
54
0
            path.tagged.as_bytes(),
55
0
            leading_tag!().len(),
56
0
            trailing_tag!().len(),
57
0
        );
58
0
        Self(hash.to_le_bytes())
59
0
    }
60
61
    /// Gets the hash value as a byte array.
62
0
    pub const fn to_bytes(self) -> [u8; 4] {
63
0
        self.0
64
0
    }
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)]
84
0
const fn fxhash_32(bytes: &[u8], ignore_leading: usize, ignore_trailing: usize) -> u32 {
85
0
    // This code is adapted from https://github.com/rust-lang/rustc-hash,
86
0
    // whose license text is reproduced below.
87
0
    //
88
0
    // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
89
0
    // file at the top-level directory of this distribution and at
90
0
    // http://rust-lang.org/COPYRIGHT.
91
0
    //
92
0
    // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
93
0
    // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
94
0
    // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
95
0
    // option. This file may not be copied, modified, or distributed
96
0
    // except according to those terms.
97
0
98
0
    if ignore_leading + ignore_trailing >= bytes.len() {
99
0
        return 0;
100
0
    }
101
102
    #[inline]
103
0
    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
0
        hash = hash.rotate_left(ROTATE);
107
0
        hash ^= word;
108
0
        hash = hash.wrapping_mul(SEED32);
109
0
        hash
110
0
    }
111
112
0
    let mut cursor = ignore_leading;
113
0
    let end = bytes.len() - ignore_trailing;
114
0
    let mut hash = 0;
115
116
0
    while end - cursor >= 4 {
117
0
        let word = u32::from_le_bytes([
118
0
            bytes[cursor],
119
0
            bytes[cursor + 1],
120
0
            bytes[cursor + 2],
121
0
            bytes[cursor + 3],
122
0
        ]);
123
0
        hash = hash_word_32(hash, word);
124
0
        cursor += 4;
125
0
    }
126
127
0
    if end - cursor >= 2 {
128
0
        let word = u16::from_le_bytes([bytes[cursor], bytes[cursor + 1]]);
129
0
        hash = hash_word_32(hash, word as u32);
130
0
        cursor += 2;
131
0
    }
132
133
0
    if end - cursor >= 1 {
134
0
        hash = hash_word_32(hash, bytes[cursor] as u32);
135
0
    }
136
137
0
    hash
138
0
}
139
140
impl<'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
147
impl AsULE for DataKeyHash {
148
    type ULE = Self;
149
    #[inline]
150
0
    fn to_unaligned(self) -> Self::ULE {
151
0
        self
152
0
    }
153
    #[inline]
154
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
155
0
        unaligned
156
0
    }
157
}
158
159
// Safe since the ULE type is `self`.
160
unsafe 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)]
164
pub 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
170
impl DataKeyPath {
171
    /// Gets the path as a static string slice.
172
    #[inline]
173
0
    pub const fn get(self) -> &'static str {
174
0
        unsafe {
175
0
            // Safe due to invariant that self.path is tagged correctly
176
0
            core::str::from_utf8_unchecked(core::slice::from_raw_parts(
177
0
                self.tagged.as_ptr().add(leading_tag!().len()),
178
0
                self.tagged.len() - trailing_tag!().len() - leading_tag!().len(),
179
0
            ))
180
0
        }
181
0
    }
182
}
183
184
impl Deref for DataKeyPath {
185
    type Target = str;
186
    #[inline]
187
0
    fn deref(&self) -> &Self::Target {
188
0
        self.get()
189
0
    }
190
}
191
192
/// Metadata statically associated with a particular [`DataKey`].
193
#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
194
#[non_exhaustive]
195
pub 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
210
impl DataKeyMetadata {
211
    /// Const-friendly version of [`Default::default`].
212
0
    pub const fn const_default() -> Self {
213
0
        Self {
214
0
            fallback_priority: LocaleFallbackPriority::const_default(),
215
0
            extension_key: None,
216
0
            fallback_supplement: None,
217
0
            singleton: false,
218
0
        }
219
0
    }
220
221
    #[doc(hidden)]
222
0
    pub const fn construct_internal(
223
0
        fallback_priority: LocaleFallbackPriority,
224
0
        extension_key: Option<icu_locid::extensions::unicode::Key>,
225
0
        fallback_supplement: Option<LocaleFallbackSupplement>,
226
0
        singleton: bool,
227
0
    ) -> Self {
228
0
        Self {
229
0
            fallback_priority,
230
0
            extension_key,
231
0
            fallback_supplement,
232
0
            singleton,
233
0
        }
234
0
    }
235
}
236
237
impl Default for DataKeyMetadata {
238
    #[inline]
239
0
    fn default() -> Self {
240
0
        Self::const_default()
241
0
    }
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)]
268
pub struct DataKey {
269
    path: DataKeyPath,
270
    hash: DataKeyHash,
271
    metadata: DataKeyMetadata,
272
}
273
274
impl PartialEq for DataKey {
275
    #[inline]
276
0
    fn eq(&self, other: &Self) -> bool {
277
0
        self.hash == other.hash && self.path == other.path && self.metadata == other.metadata
278
0
    }
279
}
280
281
impl Eq for DataKey {}
282
283
impl Ord for DataKey {
284
0
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
285
0
        self.path
286
0
            .cmp(&other.path)
287
0
            .then_with(|| self.metadata.cmp(&other.metadata))
288
0
    }
289
}
290
291
impl PartialOrd for DataKey {
292
    #[inline]
293
0
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
294
0
        Some(self.cmp(other))
295
0
    }
296
}
297
298
impl core::hash::Hash for DataKey {
299
    #[inline]
300
0
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
301
0
        self.hash.hash(state)
302
0
    }
303
}
304
305
impl 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
0
    pub const fn path(self) -> DataKeyPath {
314
0
        self.path
315
0
    }
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
0
    pub const fn hashed(self) -> DataKeyHash {
334
0
        self.hash
335
0
    }
336
337
    /// Gets the metadata associated with this [`DataKey`].
338
    #[inline]
339
0
    pub const fn metadata(self) -> DataKeyMetadata {
340
0
        self.metadata
341
0
    }
342
343
    /// Returns the [`LocaleFallbackConfig`] for this [`DataKey`].
344
    #[inline]
345
0
    pub const fn fallback_config(self) -> LocaleFallbackConfig {
346
0
        let mut config = LocaleFallbackConfig::const_default();
347
0
        config.priority = self.metadata.fallback_priority;
348
0
        config.extension_key = self.metadata.extension_key;
349
0
        config.fallback_supplement = self.metadata.fallback_supplement;
350
0
        config
351
0
    }
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
0
    pub const fn from_path_and_metadata(path: DataKeyPath, metadata: DataKeyMetadata) -> Self {
370
0
        Self {
371
0
            path,
372
0
            hash: DataKeyHash::compute_from_path(path),
373
0
            metadata,
374
0
        }
375
0
    }
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
0
    pub const fn construct_internal(
382
0
        path: &'static str,
383
0
        metadata: DataKeyMetadata,
384
0
    ) -> Result<Self, (&'static str, usize)> {
385
0
        if path.len() < leading_tag!().len() + trailing_tag!().len() {
386
0
            return Err(("tag", 0));
387
0
        }
388
0
        // Start and end of the untagged part
389
0
        let start = leading_tag!().len();
390
0
        let end = path.len() - trailing_tag!().len();
391
0
392
0
        // Check tags
393
0
        let mut i = 0;
394
0
        while i < leading_tag!().len() {
395
0
            if path.as_bytes()[i] != leading_tag!().as_bytes()[i] {
396
0
                return Err(("tag", 0));
397
0
            }
398
0
            i += 1;
399
        }
400
0
        i = 0;
401
0
        while i < trailing_tag!().len() {
402
0
            if path.as_bytes()[end + i] != trailing_tag!().as_bytes()[i] {
403
0
                return Err(("tag", end + 1));
404
0
            }
405
0
            i += 1;
406
        }
407
408
0
        match Self::validate_path_manual_slice(path, start, end) {
409
0
            Ok(()) => (),
410
0
            Err(e) => return Err(e),
411
        };
412
413
0
        let path = DataKeyPath { tagged: path };
414
0
415
0
        Ok(Self {
416
0
            path,
417
0
            hash: DataKeyHash::compute_from_path(path),
418
0
            metadata,
419
0
        })
420
0
    }
421
422
0
    const fn validate_path_manual_slice(
423
0
        path: &'static str,
424
0
        start: usize,
425
0
        end: usize,
426
0
    ) -> Result<(), (&'static str, usize)> {
427
0
        debug_assert!(start <= end);
428
0
        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
0
        let mut i = start;
438
0
        let mut state = Empty;
439
        loop {
440
0
            let byte = if i < end {
441
                #[allow(clippy::indexing_slicing)] // protected by debug assertion
442
0
                Some(path.as_bytes()[i])
443
            } else {
444
0
                None
445
            };
446
0
            state = match (state, byte) {
447
0
                (Empty | Body, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) => Body,
448
0
                (Body, Some(b'/')) => Body,
449
0
                (Body, Some(b'@')) => At,
450
0
                (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
0
                    return Ok(());
454
                }
455
456
0
                (Empty, _) => return Err(("[a-zA-Z0-9_]", i)),
457
0
                (Body, _) => return Err(("[a-zA-z0-9_/@]", i)),
458
0
                (At, _) => return Err(("[0-9]", i)),
459
0
                (Version, _) => return Err(("[0-9]", i)),
460
            };
461
0
            i += 1;
462
        }
463
0
    }
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
0
    pub fn match_key(self, key: Self) -> Result<(), DataError> {
498
0
        if self == key {
499
0
            Ok(())
500
        } else {
501
0
            Err(DataErrorKind::MissingDataKey.with_key(key))
502
        }
503
0
    }
504
}
505
506
/// See [`DataKey`].
507
#[macro_export]
508
macro_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
532
impl fmt::Debug for DataKey {
533
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
534
0
        f.write_str("DataKey{")?;
535
0
        fmt::Display::fmt(self, f)?;
536
0
        f.write_char('}')?;
537
0
        Ok(())
538
0
    }
539
}
540
541
impl Writeable for DataKey {
542
0
    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
543
0
        self.path().write_to(sink)
544
0
    }
545
546
0
    fn writeable_length_hint(&self) -> LengthHint {
547
0
        self.path().writeable_length_hint()
548
0
    }
549
550
0
    fn write_to_string(&self) -> Cow<str> {
551
0
        Cow::Borrowed(self.path().get())
552
0
    }
553
}
554
555
writeable::impl_display_with_writeable!(DataKey);
556
557
#[test]
558
fn 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]
650
fn 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]
676
fn 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]
695
fn 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
}