boa_engine/context/
icu.rs1use 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#[derive(Debug, Error, Copy, Clone)]
16pub enum IcuError {
17 #[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
36pub(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 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 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 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 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 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 pub(crate) fn erased_provider(&self) -> &dyn DynamicDryDataProvider<BufferMarker> {
147 &self.inner_provider
148 }
149}