icu_provider/
marker.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::fallback::{LocaleFallbackConfig, LocaleFallbackPriority};
6use crate::{DataError, DataErrorKind, DataLocale, DataProvider, DataProviderWithMarker};
7use core::fmt;
8use core::marker::PhantomData;
9use icu_locale_core::preferences::LocalePreferences;
10use yoke::Yokeable;
11use zerovec::ule::*;
12
13/// Trait marker for data structs. All types delivered by the data provider must be associated with
14/// something implementing this trait.
15///
16/// Data markers normally generated with the [`data_marker`](crate::data_marker) macro.
17///
18/// Also see [`DataMarker`].
19///
20/// Note: `DynamicDataMarker`s are quasi-const-generic compile-time objects, and as such are expected
21/// to be unit structs. As this is not something that can be enforced by the type system, we
22/// currently only have a `'static` bound on them (which is needed by a lot of our code).
23///
24/// # Examples
25///
26/// Manually implementing DynamicDataMarker for a custom type:
27///
28/// ```
29/// use icu_provider::prelude::*;
30/// use std::borrow::Cow;
31///
32/// #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
33/// struct MyDataStruct<'data> {
34///     message: Cow<'data, str>,
35/// }
36///
37/// struct MyDataStructMarker;
38///
39/// impl DynamicDataMarker for MyDataStructMarker {
40///     type DataStruct = MyDataStruct<'static>;
41/// }
42///
43/// // We can now use MyDataStruct with DataProvider:
44/// let s = MyDataStruct {
45///     message: Cow::Owned("Hello World".into()),
46/// };
47/// let payload = DataPayload::<MyDataStructMarker>::from_owned(s);
48/// assert_eq!(payload.get().message, "Hello World");
49/// ```
50///
51/// [`data_struct`]: crate::data_struct
52pub trait DynamicDataMarker: 'static {
53    /// A type that implements [`Yokeable`]. This should typically be the `'static` version of a
54    /// data struct.
55    type DataStruct: for<'a> Yokeable<'a>;
56}
57
58/// A [`DynamicDataMarker`] with a [`DataMarkerInfo`] attached.
59///
60/// Structs implementing this trait are normally generated with the [`data_struct!`] macro.
61///
62/// Implementing this trait enables this marker to be used with the main [`DataProvider`] trait.
63/// Most markers should be associated with a specific marker and should therefore implement this
64/// trait.
65///
66/// [`BufferMarker`] is an example of a marker that does _not_ implement this trait.
67///
68/// Note: `DataMarker`s are quasi-const-generic compile-time objects, and as such are expected
69/// to be unit structs. As this is not something that can be enforced by the type system, we
70/// currently only have a `'static` bound on them (which is needed by a lot of our code).
71///
72/// [`data_struct!`]: crate::data_struct
73/// [`DataProvider`]: crate::DataProvider
74/// [`BufferMarker`]: crate::buf::BufferMarker
75pub trait DataMarker: DynamicDataMarker {
76    /// The single [`DataMarkerInfo`] associated with this marker.
77    const INFO: DataMarkerInfo;
78}
79
80/// Extension trait for methods on [`DataMarker`]
81pub trait DataMarkerExt: DataMarker + Sized {
82    /// Binds a [`DataMarker`] to a provider supporting it.
83    fn bind<P>(provider: P) -> DataProviderWithMarker<Self, P>
84    where
85        P: DataProvider<Self>;
86    /// Constructs a [`DataLocale`] using fallback preferences from this [`DataMarker`].
87    fn make_locale(locale: LocalePreferences) -> DataLocale;
88}
89
90impl<M: DataMarker + Sized> DataMarkerExt for M {
91    fn bind<P>(provider: P) -> DataProviderWithMarker<Self, P>
92    where
93        P: DataProvider<Self>,
94    {
95        DataProviderWithMarker::new(provider)
96    }
97
98    fn make_locale(locale: LocalePreferences) -> DataLocale {
99        M::INFO.make_locale(locale)
100    }
101}
102
103/// A [`DynamicDataMarker`] that never returns data.
104///
105/// All types that have non-blanket impls of `DataProvider<M>` are expected to explicitly
106/// implement `DataProvider<NeverMarker<Y>>`, returning [`DataErrorKind::MarkerNotFound`].
107/// See [`impl_data_provider_never_marker!`].
108///
109/// [`DataErrorKind::MarkerNotFound`]: crate::DataErrorKind::MarkerNotFound
110/// [`impl_data_provider_never_marker!`]: crate::marker::impl_data_provider_never_marker
111///
112/// # Examples
113///
114/// ```
115/// use icu_locale_core::langid;
116/// use icu_provider::hello_world::*;
117/// use icu_provider::marker::NeverMarker;
118/// use icu_provider::prelude::*;
119///
120/// let buffer_provider = HelloWorldProvider.into_json_provider();
121///
122/// let result = DataProvider::<NeverMarker<HelloWorld<'static>>>::load(
123///     &buffer_provider.as_deserializing(),
124///     DataRequest {
125///         id: DataIdentifierBorrowed::for_locale(&langid!("en").into()),
126///         ..Default::default()
127///     },
128/// );
129///
130/// assert!(matches!(
131///     result,
132///     Err(DataError {
133///         kind: DataErrorKind::MarkerNotFound,
134///         ..
135///     })
136/// ));
137/// ```
138#[derive(Debug, Copy, Clone)]
139pub struct NeverMarker<Y>(PhantomData<Y>);
140
141impl<Y> DynamicDataMarker for NeverMarker<Y>
142where
143    for<'a> Y: Yokeable<'a>,
144{
145    type DataStruct = Y;
146}
147
148impl<Y> DataMarker for NeverMarker<Y>
149where
150    for<'a> Y: Yokeable<'a>,
151{
152    const INFO: DataMarkerInfo = DataMarkerInfo::from_id(DataMarkerId {
153        #[cfg(any(feature = "export", debug_assertions))]
154        debug: "NeverMarker",
155        hash: *b"nevermar",
156    });
157}
158
159/// Implements `DataProvider<NeverMarker<Y>>` on a struct.
160///
161/// For more information, see [`NeverMarker`].
162///
163/// # Examples
164///
165/// ```
166/// use icu_locale_core::langid;
167/// use icu_provider::hello_world::*;
168/// use icu_provider::marker::NeverMarker;
169/// use icu_provider::prelude::*;
170///
171/// struct MyProvider;
172///
173/// icu_provider::marker::impl_data_provider_never_marker!(MyProvider);
174///
175/// let result = DataProvider::<NeverMarker<HelloWorld<'static>>>::load(
176///     &MyProvider,
177///     DataRequest {
178///         id: DataIdentifierBorrowed::for_locale(&langid!("und").into()),
179///         ..Default::default()
180///     },
181/// );
182///
183/// assert!(matches!(
184///     result,
185///     Err(DataError {
186///         kind: DataErrorKind::MarkerNotFound,
187///         ..
188///     })
189/// ));
190/// ```
191#[doc(hidden)] // macro
192#[macro_export]
193macro_rules! __impl_data_provider_never_marker {
194    ($ty:path) => {
195        impl<Y> $crate::DataProvider<$crate::marker::NeverMarker<Y>> for $ty
196        where
197            for<'a> Y: $crate::prelude::yoke::Yokeable<'a>,
198        {
199            fn load(
200                &self,
201                req: $crate::DataRequest,
202            ) -> Result<$crate::DataResponse<$crate::marker::NeverMarker<Y>>, $crate::DataError>
203            {
204                Err($crate::DataErrorKind::MarkerNotFound.with_req(
205                    <$crate::marker::NeverMarker<Y> as $crate::DataMarker>::INFO,
206                    req,
207                ))
208            }
209        }
210    };
211}
212#[doc(inline)]
213pub use __impl_data_provider_never_marker as impl_data_provider_never_marker;
214
215/// A compact hash of a [`DataMarkerInfo`]. Useful for keys in maps.
216///
217/// The hash will be stable over time within major releases.
218#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, ULE)]
219#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
220#[repr(transparent)]
221pub struct DataMarkerIdHash([u8; 4]);
222
223impl DataMarkerIdHash {
224    /// Gets the hash value as a byte array.
225    pub const fn to_bytes(self) -> [u8; 4] {
226        self.0
227    }
228}
229
230/// Const function to compute the FxHash of a byte array.
231///
232/// FxHash is a speedy hash algorithm used within rustc. The algorithm is satisfactory for our
233/// use case since the strings being hashed originate from a trusted source (the ICU4X
234/// components), and the hashes are computed at compile time, so we can check for collisions.
235///
236/// We could have considered a SHA or other cryptographic hash function. However, we are using
237/// FxHash because:
238///
239/// 1. There is precedent for this algorithm in Rust
240/// 2. The algorithm is easy to implement as a const function
241/// 3. The amount of code is small enough that we can reasonably keep the algorithm in-tree
242/// 4. FxHash is designed to output 32-bit or 64-bit values, whereas SHA outputs more bits,
243///    such that truncation would be required in order to fit into a u32, partially reducing
244///    the benefit of a cryptographically secure algorithm
245// The indexing operations in this function have been reviewed in detail and won't panic.
246#[allow(clippy::indexing_slicing)]
247const fn fxhash_32(bytes: &[u8]) -> u32 {
248    // This code is adapted from https://github.com/rust-lang/rustc-hash,
249    // whose license text is reproduced below.
250    //
251    // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
252    // file at the top-level directory of this distribution and at
253    // http://rust-lang.org/COPYRIGHT.
254    //
255    // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
256    // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
257    // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
258    // option. This file may not be copied, modified, or distributed
259    // except according to those terms.
260
261    #[inline]
262    const fn hash_word_32(mut hash: u32, word: u32) -> u32 {
263        const ROTATE: u32 = 5;
264        const SEED32: u32 = 0x9e_37_79_b9;
265        hash = hash.rotate_left(ROTATE);
266        hash ^= word;
267        hash = hash.wrapping_mul(SEED32);
268        hash
269    }
270
271    let mut cursor = 0;
272    let end = bytes.len();
273    let mut hash = 0;
274
275    while end - cursor >= 4 {
276        let word = u32::from_le_bytes([
277            bytes[cursor],
278            bytes[cursor + 1],
279            bytes[cursor + 2],
280            bytes[cursor + 3],
281        ]);
282        hash = hash_word_32(hash, word);
283        cursor += 4;
284    }
285
286    if end - cursor >= 2 {
287        let word = u16::from_le_bytes([bytes[cursor], bytes[cursor + 1]]);
288        hash = hash_word_32(hash, word as u32);
289        cursor += 2;
290    }
291
292    if end - cursor >= 1 {
293        hash = hash_word_32(hash, bytes[cursor] as u32);
294    }
295
296    hash
297}
298
299#[cfg(feature = "alloc")]
300impl<'a> zerovec::maps::ZeroMapKV<'a> for DataMarkerIdHash {
301    type Container = zerovec::ZeroVec<'a, DataMarkerIdHash>;
302    type Slice = zerovec::ZeroSlice<DataMarkerIdHash>;
303    type GetType = <DataMarkerIdHash as AsULE>::ULE;
304    type OwnedType = DataMarkerIdHash;
305}
306
307impl AsULE for DataMarkerIdHash {
308    type ULE = Self;
309    #[inline]
310    fn to_unaligned(self) -> Self::ULE {
311        self
312    }
313    #[inline]
314    fn from_unaligned(unaligned: Self::ULE) -> Self {
315        unaligned
316    }
317}
318
319// Safe since the ULE type is `self`.
320unsafe impl EqULE for DataMarkerIdHash {}
321
322/// The ID of a data marker.
323///
324/// This is generally a [`DataMarkerIdHash`]. If debug assertions or the `export` Cargo feature
325/// are enabled, this also contains a human-readable string for an improved `Debug` implementation.
326#[derive(Debug, Copy, Clone, Eq)]
327pub struct DataMarkerId {
328    /// The human-readable path string ends with `@` followed by one or more digits (the version
329    /// number). Paths do not contain characters other than ASCII letters and digits, `_`, `/`.
330    #[cfg(any(feature = "export", debug_assertions))]
331    debug: &'static str,
332    hash: [u8; 8],
333}
334
335impl PartialEq for DataMarkerId {
336    #[inline]
337    fn eq(&self, other: &Self) -> bool {
338        self.hash == other.hash
339    }
340}
341
342impl Ord for DataMarkerId {
343    #[inline]
344    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
345        self.hash.cmp(&other.hash)
346    }
347}
348
349impl PartialOrd for DataMarkerId {
350    #[inline]
351    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
352        Some(self.hash.cmp(&other.hash))
353    }
354}
355
356impl core::hash::Hash for DataMarkerId {
357    #[inline]
358    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
359        self.hash.hash(state)
360    }
361}
362
363impl DataMarkerId {
364    #[doc(hidden)]
365    // macro use
366    // Error is a str of the expected character class and the index where it wasn't encountered
367    // The indexing operations in this function have been reviewed in detail and won't panic.
368    #[allow(clippy::indexing_slicing)]
369    pub const fn construct_internal(name: &'static str) -> Result<Self, (&'static str, usize)> {
370        match Self::validate_marker_name(name) {
371            Ok(()) => (),
372            Err(e) => return Err(e),
373        };
374
375        let hash = fxhash_32(name.as_bytes()).to_le_bytes();
376
377        Ok(Self {
378            #[cfg(any(feature = "export", debug_assertions))]
379            debug: name,
380            hash: [b't', b'd', b'm', b'h', hash[0], hash[1], hash[2], hash[3]],
381        })
382    }
383
384    const fn validate_marker_name(path: &'static str) -> Result<(), (&'static str, usize)> {
385        #![allow(clippy::indexing_slicing)]
386        if !path.as_bytes()[path.len() - 1].is_ascii_digit() {
387            return Err(("[0-9]", path.len()));
388        }
389        let mut i = path.len() - 1;
390        while path.as_bytes()[i - 1].is_ascii_digit() {
391            i -= 1;
392        }
393        if path.as_bytes()[i - 1] != b'V' {
394            return Err(("V", i));
395        }
396        Ok(())
397    }
398
399    /// Gets a platform-independent hash of a [`DataMarkerId`].
400    ///
401    /// The hash is 4 bytes and allows for fast comparison.
402    ///
403    /// # Example
404    ///
405    /// ```
406    /// use icu_provider::prelude::*;
407    ///
408    /// icu_provider::data_marker!(FooV1, &'static str);
409    ///
410    /// assert_eq!(FooV1::INFO.id.hashed().to_bytes(), [198, 217, 86, 48]);
411    /// ```
412    #[inline]
413    pub const fn hashed(self) -> DataMarkerIdHash {
414        let [.., h1, h2, h3, h4] = self.hash;
415        DataMarkerIdHash([h1, h2, h3, h4])
416    }
417}
418
419/// Used for loading data from a dynamic ICU4X data provider.
420///
421/// A data marker is tightly coupled with the code that uses it to load data at runtime.
422/// Executables can be searched for `DataMarkerInfo` instances to produce optimized data files.
423/// Therefore, users should not generally create DataMarkerInfo instances; they should instead use
424/// the ones exported by a component.
425#[derive(Copy, Clone, PartialEq, Eq)]
426#[non_exhaustive]
427pub struct DataMarkerInfo {
428    /// The ID of this marker.
429    pub id: DataMarkerId,
430    /// Whether this data marker only has a single payload, not keyed by a data identifier.
431    pub is_singleton: bool,
432    /// Whether this data marker uses checksums for integrity purposes.
433    pub has_checksum: bool,
434    /// The fallback to use for this data marker.
435    pub fallback_config: LocaleFallbackConfig,
436    /// The attributes domain for this data marker. This can be used for filtering marker
437    /// attributes during provider export.
438    #[cfg(feature = "export")]
439    pub attributes_domain: &'static str,
440}
441
442impl PartialOrd for DataMarkerInfo {
443    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
444        Some(self.id.cmp(&other.id))
445    }
446}
447
448impl Ord for DataMarkerInfo {
449    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
450        self.id.cmp(&other.id)
451    }
452}
453
454impl core::hash::Hash for DataMarkerInfo {
455    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
456        self.id.hash(state)
457    }
458}
459
460impl DataMarkerInfo {
461    /// See [`Default::default`]
462    pub const fn from_id(id: DataMarkerId) -> Self {
463        Self {
464            id,
465            fallback_config: LocaleFallbackConfig::default(),
466            is_singleton: false,
467            has_checksum: false,
468            #[cfg(feature = "export")]
469            attributes_domain: "",
470        }
471    }
472
473    /// Returns [`Ok`] if this data marker matches the argument, or the appropriate error.
474    ///
475    /// Convenience method for data providers that support a single [`DataMarkerInfo`].
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// use icu_provider::hello_world::*;
481    /// use icu_provider::prelude::*;
482    ///
483    /// icu_provider::data_marker!(
484    ///     DummyV1,
485    ///     <HelloWorldV1 as DynamicDataMarker>::DataStruct
486    /// );
487    ///
488    /// assert!(matches!(
489    ///     HelloWorldV1::INFO.match_marker(HelloWorldV1::INFO),
490    ///     Ok(())
491    /// ));
492    /// assert!(matches!(
493    ///     HelloWorldV1::INFO.match_marker(DummyV1::INFO),
494    ///     Err(DataError {
495    ///         kind: DataErrorKind::MarkerNotFound,
496    ///         ..
497    ///     })
498    /// ));
499    ///
500    /// // The error context contains the argument:
501    /// assert_eq!(
502    ///     HelloWorldV1::INFO
503    ///         .match_marker(DummyV1::INFO)
504    ///         .unwrap_err()
505    ///         .marker,
506    ///     Some(DummyV1::INFO.id)
507    /// );
508    /// ```
509    pub fn match_marker(self, marker: Self) -> Result<(), DataError> {
510        if self == marker {
511            Ok(())
512        } else {
513            Err(DataErrorKind::MarkerNotFound.with_marker(marker))
514        }
515    }
516
517    /// Constructs a [`DataLocale`] for this [`DataMarkerInfo`].
518    pub fn make_locale(self, locale: LocalePreferences) -> DataLocale {
519        if self.fallback_config.priority == LocaleFallbackPriority::Region {
520            locale.to_data_locale_region_priority()
521        } else {
522            locale.to_data_locale_language_priority()
523        }
524    }
525}
526
527/// See [`DataMarkerInfo`].
528#[doc(hidden)] // macro
529#[macro_export]
530macro_rules! __data_marker_id {
531    ($name:ident) => {
532        const {
533            match $crate::marker::DataMarkerId::construct_internal(stringify!($name)) {
534                Ok(path) => path,
535                #[allow(clippy::panic)] // Const context
536                Err(_) => panic!(concat!("Invalid marker name: ", stringify!($name))),
537            }
538        }
539    };
540}
541#[doc(inline)]
542pub use __data_marker_id as data_marker_id;
543
544/// Creates a data marker.
545///
546/// # Examples
547///
548/// ```
549/// icu_provider::data_marker!(DummyV1, &'static str);
550/// ```
551///
552/// The identifier needs to end with a `V` followed by one or more digits (the version number).
553///
554/// Invalid identifiers are compile-time errors (as [`data_marker!`](crate::data_marker) uses `const`).
555///
556/// ```compile_fail,E0080
557/// icu_provider::data_marker!(Dummy, &'static str);
558/// ```
559#[macro_export] // canonical location is crate root
560macro_rules! data_marker {
561    ($(#[$doc:meta])* $name:ident, $($debug:literal,)? $struct:ty $(, $(#[$meta:meta])* $info_field:ident = $info_val:expr)* $(,)?) => {
562        $(#[$doc])*
563        #[non_exhaustive]
564        pub struct $name;
565        impl $crate::DynamicDataMarker for $name {
566            type DataStruct = $struct;
567        }
568        impl $crate::DataMarker for $name {
569            const INFO: $crate::DataMarkerInfo = {
570                $(
571                    /// ```rust
572                    #[doc = concat!("let ident = \"", stringify!($name), "\";")]
573                    #[doc = concat!("let debug = \"", $debug, "\";")]
574                    /// assert_eq!(
575                    ///     debug.split('/').map(|s| {
576                    ///         let mut b = s.to_ascii_lowercase().into_bytes();
577                    ///         b[0] = b[0].to_ascii_uppercase();
578                    ///         String::from_utf8(b).unwrap()
579                    ///     })
580                    ///     .collect::<Vec<_>>()
581                    ///     .join(""),
582                    ///     ident
583                    /// );
584                    /// ```
585                    #[allow(dead_code)]
586                    struct DebugTest;
587                )?
588                #[allow(unused_mut)]
589                let mut info = $crate::DataMarkerInfo::from_id($crate::marker::data_marker_id!($name));
590                $(
591                    $(#[$meta])*
592                    {info.$info_field = $info_val;}
593                )*
594                info
595            };
596        }
597    }
598}
599
600impl fmt::Debug for DataMarkerInfo {
601    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
602        #[cfg(any(feature = "export", debug_assertions))]
603        return f.write_str(self.id.debug);
604        #[cfg(not(any(feature = "export", debug_assertions)))]
605        return write!(f, "{:?}", self.id);
606    }
607}
608
609/// A marker for the given `DataStruct`.
610#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
611pub struct ErasedMarker<DataStruct: for<'a> Yokeable<'a>>(PhantomData<DataStruct>);
612impl<DataStruct: for<'a> Yokeable<'a>> DynamicDataMarker for ErasedMarker<DataStruct> {
613    type DataStruct = DataStruct;
614}
615
616#[test]
617fn test_marker_syntax() {
618    // Valid markers:
619    DataMarkerId::construct_internal("HelloWorldV1").unwrap();
620    DataMarkerId::construct_internal("HelloWorldFooV1").unwrap();
621    DataMarkerId::construct_internal("HelloWorldV999").unwrap();
622    DataMarkerId::construct_internal("Hello485FooV1").unwrap();
623
624    // No version:
625    assert_eq!(
626        DataMarkerId::construct_internal("HelloWorld"),
627        Err(("[0-9]", "HelloWorld".len()))
628    );
629
630    assert_eq!(
631        DataMarkerId::construct_internal("HelloWorldV"),
632        Err(("[0-9]", "HelloWorldV".len()))
633    );
634    assert_eq!(
635        DataMarkerId::construct_internal("HelloWorldVFoo"),
636        Err(("[0-9]", "HelloWorldVFoo".len()))
637    );
638    assert_eq!(
639        DataMarkerId::construct_internal("HelloWorldV1Foo"),
640        Err(("[0-9]", "HelloWorldV1Foo".len()))
641    );
642}
643
644#[test]
645fn test_id_debug() {
646    assert_eq!(data_marker_id!(BarV1).debug, "BarV1");
647}
648
649#[test]
650fn test_hash_word_32() {
651    assert_eq!(0, fxhash_32(b""));
652    assert_eq!(0xF3051F19, fxhash_32(b"a"));
653    assert_eq!(0x2F9DF119, fxhash_32(b"ab"));
654    assert_eq!(0xCB1D9396, fxhash_32(b"abc"));
655    assert_eq!(0x8628F119, fxhash_32(b"abcd"));
656    assert_eq!(0xBEBDB56D, fxhash_32(b"abcde"));
657    assert_eq!(0x1CE8476D, fxhash_32(b"abcdef"));
658    assert_eq!(0xC0F176A4, fxhash_32(b"abcdefg"));
659    assert_eq!(0x09AB476D, fxhash_32(b"abcdefgh"));
660    assert_eq!(0xB72F5D88, fxhash_32(b"abcdefghi"));
661}
662
663#[test]
664fn test_id_hash() {
665    assert_eq!(
666        data_marker_id!(BarV1).hashed(),
667        DataMarkerIdHash([212, 77, 158, 241]),
668    );
669}