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