Coverage Report

Created: 2026-02-26 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/icu_provider-2.1.1/src/request.rs
Line
Count
Source
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
#[cfg(feature = "alloc")]
6
use alloc::borrow::Cow;
7
#[cfg(feature = "alloc")]
8
use alloc::borrow::ToOwned;
9
#[cfg(feature = "alloc")]
10
use alloc::boxed::Box;
11
#[cfg(feature = "alloc")]
12
use alloc::string::String;
13
#[cfg(feature = "alloc")]
14
use core::cmp::Ordering;
15
use core::default::Default;
16
use core::fmt;
17
use core::fmt::Debug;
18
use core::hash::Hash;
19
use core::ops::Deref;
20
#[cfg(feature = "alloc")]
21
use zerovec::ule::VarULE;
22
23
pub use icu_locale_core::DataLocale;
24
25
/// The request type passed into all data provider implementations.
26
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
27
#[allow(clippy::exhaustive_structs)] // this type is stable
28
pub struct DataRequest<'a> {
29
    /// The data identifier for which to load data.
30
    ///
31
    /// If locale fallback is enabled, the resulting data may be from a different identifier
32
    /// than the one requested here.
33
    pub id: DataIdentifierBorrowed<'a>,
34
    /// Metadata that may affect the behavior of the data provider.
35
    pub metadata: DataRequestMetadata,
36
}
37
38
/// Metadata for data requests. This is currently empty, but it may be extended with options
39
/// for tuning locale fallback, buffer layout, and so forth.
40
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41
#[non_exhaustive]
42
pub struct DataRequestMetadata {
43
    /// Silent requests do not log errors. This can be used for exploratory querying, such as fallbacks.
44
    pub silent: bool,
45
    /// Whether to allow prefix matches for the data marker attributes.
46
    pub attributes_prefix_match: bool,
47
}
48
49
/// The borrowed version of a [`DataIdentifierCow`].
50
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
51
#[non_exhaustive]
52
pub struct DataIdentifierBorrowed<'a> {
53
    /// Marker-specific request attributes
54
    pub marker_attributes: &'a DataMarkerAttributes,
55
    /// The CLDR locale
56
    pub locale: &'a DataLocale,
57
}
58
59
impl fmt::Display for DataIdentifierBorrowed<'_> {
60
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61
0
        fmt::Display::fmt(self.locale, f)?;
62
0
        if !self.marker_attributes.is_empty() {
63
0
            write!(f, "/{}", self.marker_attributes.as_str())?;
64
0
        }
65
0
        Ok(())
66
0
    }
67
}
68
69
impl<'a> DataIdentifierBorrowed<'a> {
70
    /// Creates a [`DataIdentifierBorrowed`] for a borrowed [`DataLocale`].
71
0
    pub fn for_locale(locale: &'a DataLocale) -> Self {
72
0
        Self {
73
0
            locale,
74
0
            ..Default::default()
75
0
        }
76
0
    }
77
78
    /// Creates a [`DataIdentifierBorrowed`] for a borrowed [`DataMarkerAttributes`].
79
0
    pub fn for_marker_attributes(marker_attributes: &'a DataMarkerAttributes) -> Self {
80
0
        Self {
81
0
            marker_attributes,
82
0
            ..Default::default()
83
0
        }
84
0
    }
85
86
    /// Creates a [`DataIdentifierBorrowed`] for a borrowed [`DataMarkerAttributes`] and [`DataLocale`].
87
0
    pub fn for_marker_attributes_and_locale(
88
0
        marker_attributes: &'a DataMarkerAttributes,
89
0
        locale: &'a DataLocale,
90
0
    ) -> Self {
91
0
        Self {
92
0
            marker_attributes,
93
0
            locale,
94
0
        }
95
0
    }
96
97
    /// Converts this [`DataIdentifierBorrowed`] into a [`DataIdentifierCow<'static>`].
98
    ///
99
    /// ✨ *Enabled with the `alloc` Cargo feature.*
100
    #[cfg(feature = "alloc")]
101
    pub fn into_owned(self) -> DataIdentifierCow<'static> {
102
        DataIdentifierCow {
103
            marker_attributes: Cow::Owned(self.marker_attributes.to_owned()),
104
            locale: *self.locale,
105
        }
106
    }
107
108
    /// Borrows this [`DataIdentifierBorrowed`] as a [`DataIdentifierCow<'a>`].
109
    ///
110
    /// ✨ *Enabled with the `alloc` Cargo feature.*
111
    #[cfg(feature = "alloc")]
112
    pub fn as_cow(self) -> DataIdentifierCow<'a> {
113
        DataIdentifierCow {
114
            marker_attributes: Cow::Borrowed(self.marker_attributes),
115
            locale: *self.locale,
116
        }
117
    }
118
}
119
120
/// A data identifier identifies a particular version of data, such as "English".
121
///
122
/// It is a wrapper around a [`DataLocale`] and a [`DataMarkerAttributes`].
123
///
124
/// ✨ *Enabled with the `alloc` Cargo feature.*
125
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
126
#[non_exhaustive]
127
#[cfg(feature = "alloc")]
128
pub struct DataIdentifierCow<'a> {
129
    /// Marker-specific request attributes
130
    pub marker_attributes: Cow<'a, DataMarkerAttributes>,
131
    /// The CLDR locale
132
    pub locale: DataLocale,
133
}
134
135
#[cfg(feature = "alloc")]
136
impl PartialOrd for DataIdentifierCow<'_> {
137
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
138
        Some(self.cmp(other))
139
    }
140
}
141
142
#[cfg(feature = "alloc")]
143
impl Ord for DataIdentifierCow<'_> {
144
    fn cmp(&self, other: &Self) -> Ordering {
145
        self.marker_attributes
146
            .cmp(&other.marker_attributes)
147
            .then_with(|| self.locale.total_cmp(&other.locale))
148
    }
149
}
150
151
#[cfg(feature = "alloc")]
152
impl fmt::Display for DataIdentifierCow<'_> {
153
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154
        fmt::Display::fmt(&self.locale, f)?;
155
        if !self.marker_attributes.is_empty() {
156
            write!(f, "/{}", self.marker_attributes.as_str())?;
157
        }
158
        Ok(())
159
    }
160
}
161
162
#[cfg(feature = "alloc")]
163
impl<'a> DataIdentifierCow<'a> {
164
    /// Borrows this [`DataIdentifierCow`] as a [`DataIdentifierBorrowed<'a>`].
165
    pub fn as_borrowed(&'a self) -> DataIdentifierBorrowed<'a> {
166
        DataIdentifierBorrowed {
167
            marker_attributes: &self.marker_attributes,
168
            locale: &self.locale,
169
        }
170
    }
171
172
    /// Creates a [`DataIdentifierCow`] from an owned [`DataLocale`].
173
    pub fn from_locale(locale: DataLocale) -> Self {
174
        Self {
175
            marker_attributes: Cow::Borrowed(DataMarkerAttributes::empty()),
176
            locale,
177
        }
178
    }
179
180
    /// Creates a [`DataIdentifierCow`] from a borrowed [`DataMarkerAttributes`].
181
    pub fn from_marker_attributes(marker_attributes: &'a DataMarkerAttributes) -> Self {
182
        Self {
183
            marker_attributes: Cow::Borrowed(marker_attributes),
184
            locale: Default::default(),
185
        }
186
    }
187
188
    /// Creates a [`DataIdentifierCow`] from an owned [`DataMarkerAttributes`].
189
    pub fn from_marker_attributes_owned(marker_attributes: Box<DataMarkerAttributes>) -> Self {
190
        Self {
191
            marker_attributes: Cow::Owned(marker_attributes),
192
            locale: Default::default(),
193
        }
194
    }
195
196
    /// Creates a [`DataIdentifierCow`] from an owned [`DataMarkerAttributes`] and an owned [`DataLocale`].
197
    pub fn from_owned(marker_attributes: Box<DataMarkerAttributes>, locale: DataLocale) -> Self {
198
        Self {
199
            marker_attributes: Cow::Owned(marker_attributes),
200
            locale,
201
        }
202
    }
203
204
    /// Creates a [`DataIdentifierCow`] from a borrowed [`DataMarkerAttributes`] and an owned [`DataLocale`].
205
    pub fn from_borrowed_and_owned(
206
        marker_attributes: &'a DataMarkerAttributes,
207
        locale: DataLocale,
208
    ) -> Self {
209
        Self {
210
            marker_attributes: Cow::Borrowed(marker_attributes),
211
            locale,
212
        }
213
    }
214
215
    /// Returns whether this id is equal to the default.
216
    pub fn is_unknown(&self) -> bool {
217
        self.marker_attributes.is_empty() && self.locale.is_unknown()
218
    }
219
}
220
221
#[cfg(feature = "alloc")]
222
impl Default for DataIdentifierCow<'_> {
223
    fn default() -> Self {
224
        Self {
225
            marker_attributes: Cow::Borrowed(Default::default()),
226
            locale: Default::default(),
227
        }
228
    }
229
}
230
231
/// An additional key to identify data beyond a [`DataLocale`].
232
///
233
/// The is a loose wrapper around a string, with semantics defined by each [`DataMarker`](crate::DataMarker).
234
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash)]
235
#[repr(transparent)]
236
pub struct DataMarkerAttributes {
237
    // Validated to be non-empty ASCII alphanumeric + hyphen + underscore
238
    value: str,
239
}
240
241
impl Default for &DataMarkerAttributes {
242
0
    fn default() -> Self {
243
0
        DataMarkerAttributes::empty()
244
0
    }
245
}
246
247
impl Deref for DataMarkerAttributes {
248
    type Target = str;
249
    #[inline]
250
0
    fn deref(&self) -> &Self::Target {
251
0
        &self.value
252
0
    }
253
}
254
255
impl Debug for DataMarkerAttributes {
256
    #[inline]
257
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258
0
        self.value.fmt(f)
259
0
    }
260
}
261
262
/// Invalid character
263
#[derive(Debug)]
264
#[non_exhaustive]
265
pub struct AttributeParseError;
266
267
impl DataMarkerAttributes {
268
    /// Safety-usable invariant: validated bytes are ASCII only
269
0
    const fn validate(s: &[u8]) -> Result<(), AttributeParseError> {
270
0
        let mut i = 0;
271
0
        while i < s.len() {
272
            #[expect(clippy::indexing_slicing)] // duh
273
0
            if !matches!(s[i], b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_') {
274
0
                return Err(AttributeParseError);
275
0
            }
276
0
            i += 1;
277
        }
278
0
        Ok(())
279
0
    }
280
281
    /// Creates a borrowed [`DataMarkerAttributes`] from a borrowed string.
282
    ///
283
    /// Returns an error if the string contains characters other than `[a-zA-Z0-9_\-]`.
284
0
    pub const fn try_from_str(s: &str) -> Result<&Self, AttributeParseError> {
285
0
        Self::try_from_utf8(s.as_bytes())
286
0
    }
287
288
    /// Attempts to create a borrowed [`DataMarkerAttributes`] from a borrowed UTF-8 encoded byte slice.
289
    ///
290
    /// # Examples
291
    ///
292
    /// ```
293
    /// use icu_provider::prelude::*;
294
    ///
295
    /// let bytes = b"long-meter";
296
    /// let marker = DataMarkerAttributes::try_from_utf8(bytes).unwrap();
297
    /// assert_eq!(marker.to_string(), "long-meter");
298
    /// ```
299
    ///
300
    /// # Errors
301
    ///
302
    /// Returns an error if the byte slice contains code units other than `[a-zA-Z0-9_\-]`.
303
0
    pub const fn try_from_utf8(code_units: &[u8]) -> Result<&Self, AttributeParseError> {
304
0
        let Ok(()) = Self::validate(code_units) else {
305
0
            return Err(AttributeParseError);
306
        };
307
308
        // SAFETY: `validate` requires a UTF-8 subset
309
0
        let s = unsafe { core::str::from_utf8_unchecked(code_units) };
310
311
        // SAFETY: `Self` has the same layout as `str`
312
0
        Ok(unsafe { &*(s as *const str as *const Self) })
313
0
    }
314
315
    /// Creates an owned [`DataMarkerAttributes`] from an owned string.
316
    ///
317
    /// Returns an error if the string contains characters other than `[a-zA-Z0-9_\-]`.
318
    ///
319
    /// ✨ *Enabled with the `alloc` Cargo feature.*
320
    #[cfg(feature = "alloc")]
321
    pub fn try_from_string(s: String) -> Result<Box<Self>, AttributeParseError> {
322
        let Ok(()) = Self::validate(s.as_bytes()) else {
323
            return Err(AttributeParseError);
324
        };
325
326
        // SAFETY: `Self` has the same layout as `str`
327
        Ok(unsafe { core::mem::transmute::<Box<str>, Box<Self>>(s.into_boxed_str()) })
328
    }
329
330
    /// Creates a borrowed [`DataMarkerAttributes`] from a borrowed string.
331
    ///
332
    /// Panics if the string contains characters other than `[a-zA-Z0-9_\-]`.
333
0
    pub const fn from_str_or_panic(s: &str) -> &Self {
334
0
        let Ok(r) = Self::try_from_str(s) else {
335
0
            panic!("Invalid marker attribute syntax")
336
        };
337
0
        r
338
0
    }
339
340
    /// Creates an empty [`DataMarkerAttributes`].
341
0
    pub const fn empty() -> &'static Self {
342
        // SAFETY: `Self` has the same layout as `str`
343
0
        unsafe { &*("" as *const str as *const Self) }
344
0
    }
345
346
    /// Returns this [`DataMarkerAttributes`] as a `&str`.
347
0
    pub const fn as_str(&self) -> &str {
348
0
        &self.value
349
0
    }
350
}
351
352
/// ✨ *Enabled with the `alloc` Cargo feature.*
353
#[cfg(feature = "alloc")]
354
impl ToOwned for DataMarkerAttributes {
355
    type Owned = Box<Self>;
356
    fn to_owned(&self) -> Self::Owned {
357
        // SAFETY: `Self` has the same layout as `str`
358
        unsafe { core::mem::transmute::<Box<str>, Box<Self>>(self.as_str().to_boxed()) }
359
    }
360
}
361
362
#[test]
363
fn test_data_marker_attributes_from_utf8() {
364
    let bytes_vec: Vec<&[u8]> = vec![
365
        b"long-meter",
366
        b"long",
367
        b"meter",
368
        b"short-meter-second",
369
        b"usd",
370
    ];
371
372
    for bytes in bytes_vec {
373
        let marker = DataMarkerAttributes::try_from_utf8(bytes).unwrap();
374
        assert_eq!(marker.to_string().as_bytes(), bytes);
375
    }
376
}