boa_engine/context/
icu.rs

1use std::{cell::OnceCell, fmt::Debug};
2
3use icu_casemap::CaseMapper;
4use icu_locale::{LocaleCanonicalizer, LocaleExpander};
5use icu_normalizer::{ComposingNormalizer, DecomposingNormalizer};
6use icu_provider::prelude::*;
7use serde::Deserialize;
8use thiserror::Error;
9use yoke::Yokeable;
10use zerofrom::ZeroFrom;
11
12use crate::{JsError, JsNativeError, builtins::string::StringNormalizers};
13
14/// Error thrown when the engine cannot initialize the ICU4X utilities from a data provider.
15#[derive(Debug, Error, Copy, Clone)]
16pub enum IcuError {
17    /// Failed to create the internationalization utilities.
18    #[error("could not construct the internationalization utilities")]
19    DataError(#[from] DataError),
20}
21
22impl From<IcuError> for JsNativeError {
23    #[cfg_attr(feature = "native-backtrace", track_caller)]
24    fn from(value: IcuError) -> Self {
25        JsNativeError::typ().with_message(value.to_string())
26    }
27}
28
29impl From<IcuError> for JsError {
30    #[cfg_attr(feature = "native-backtrace", track_caller)]
31    fn from(value: IcuError) -> Self {
32        JsNativeError::from(value).into()
33    }
34}
35
36/// Custom [`DataProvider`] for `Intl` that caches some utilities.
37pub(crate) struct IntlProvider {
38    inner_provider: Box<dyn DynamicDryDataProvider<BufferMarker>>,
39    locale_canonicalizer: OnceCell<LocaleCanonicalizer>,
40    locale_expander: OnceCell<LocaleExpander>,
41    string_normalizers: OnceCell<StringNormalizers>,
42    case_mapper: OnceCell<CaseMapper>,
43}
44
45impl<M> DataProvider<M> for IntlProvider
46where
47    M: DataMarker + 'static,
48    for<'de> <M::DataStruct as Yokeable<'de>>::Output: Deserialize<'de> + Clone,
49    M::DataStruct: ZeroFrom<'static, M::DataStruct>,
50{
51    fn load(&self, req: DataRequest<'_>) -> Result<DataResponse<M>, DataError> {
52        self.inner_provider
53            .as_deserializing()
54            .load_data(M::INFO, req)
55    }
56}
57
58impl<M> DryDataProvider<M> for IntlProvider
59where
60    M: DataMarker + 'static,
61    for<'de> <M::DataStruct as Yokeable<'de>>::Output: Deserialize<'de> + Clone,
62    M::DataStruct: ZeroFrom<'static, M::DataStruct>,
63{
64    fn dry_load(&self, req: DataRequest<'_>) -> Result<DataResponseMetadata, DataError> {
65        self.inner_provider.dry_load_data(M::INFO, req)
66    }
67}
68
69impl Debug for IntlProvider {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        f.debug_struct("Icu")
72            .field("locale_canonicalizer", &self.locale_canonicalizer)
73            .field("locale_expander", &self.locale_expander)
74            .field("string_normalizers", &self.string_normalizers)
75            .field("string_normalizercase_mapper", &self.case_mapper)
76            .finish_non_exhaustive()
77    }
78}
79
80impl IntlProvider {
81    /// Creates a new [`IntlProvider`] from a [`DynamicDryDataProvider<BufferMarker>`].
82    ///
83    /// # Errors
84    ///
85    /// Returns an error if any of the tools required cannot be constructed.
86    pub(crate) fn try_new_buffer(
87        provider: impl DynamicDryDataProvider<BufferMarker> + 'static,
88    ) -> IntlProvider {
89        Self {
90            locale_canonicalizer: OnceCell::new(),
91            locale_expander: OnceCell::new(),
92            string_normalizers: OnceCell::new(),
93            case_mapper: OnceCell::new(),
94            inner_provider: Box::new(provider),
95        }
96    }
97
98    /// Gets the [`LocaleCanonicalizer`] tool.
99    pub(crate) fn locale_canonicalizer(&self) -> Result<&LocaleCanonicalizer, IcuError> {
100        if let Some(lc) = self.locale_canonicalizer.get() {
101            return Ok(lc);
102        }
103        let expander = self.locale_expander()?.clone();
104        let lc = LocaleCanonicalizer::try_new_with_expander_with_buffer_provider(
105            &self.inner_provider,
106            expander,
107        )?;
108        Ok(self.locale_canonicalizer.get_or_init(|| lc))
109    }
110
111    /// Gets the [`LocaleExpander`] tool.
112    pub(crate) fn locale_expander(&self) -> Result<&LocaleExpander, IcuError> {
113        if let Some(le) = self.locale_expander.get() {
114            return Ok(le);
115        }
116
117        let le = LocaleExpander::try_new_extended_with_buffer_provider(&self.inner_provider)?;
118        Ok(self.locale_expander.get_or_init(|| le))
119    }
120
121    /// Gets the [`StringNormalizers`] tools.
122    pub(crate) fn string_normalizers(&self) -> Result<&StringNormalizers, IcuError> {
123        if let Some(sn) = self.string_normalizers.get() {
124            return Ok(sn);
125        }
126        let sn = StringNormalizers {
127            nfc: ComposingNormalizer::try_new_nfc_with_buffer_provider(&self.inner_provider)?,
128            nfkc: ComposingNormalizer::try_new_nfkc_with_buffer_provider(&self.inner_provider)?,
129            nfd: DecomposingNormalizer::try_new_nfd_with_buffer_provider(&self.inner_provider)?,
130            nfkd: DecomposingNormalizer::try_new_nfkd_with_buffer_provider(&self.inner_provider)?,
131        };
132        Ok(self.string_normalizers.get_or_init(|| sn))
133    }
134
135    /// Gets the [`CaseMapper`] tool.
136    pub(crate) fn case_mapper(&self) -> Result<&CaseMapper, IcuError> {
137        if let Some(cm) = self.case_mapper.get() {
138            return Ok(cm);
139        }
140        let cm = CaseMapper::try_new_with_buffer_provider(&self.inner_provider)?;
141
142        Ok(self.case_mapper.get_or_init(|| cm))
143    }
144
145    /// Gets the inner provider.
146    pub(crate) fn erased_provider(&self) -> &dyn DynamicDryDataProvider<BufferMarker> {
147        &self.inner_provider
148    }
149}