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::buf::BufferFormat;
6use crate::prelude::*;
7use core::fmt;
8use displaydoc::Display;
9
10/// A list specifying general categories of data provider error.
11///
12/// Errors may be caused either by a malformed request or by the data provider
13/// not being able to fulfill a well-formed request.
14#[derive(Clone, Copy, Eq, PartialEq, Display, Debug)]
15#[non_exhaustive]
16pub enum DataErrorKind {
17 /// No data for the provided resource key.
18 #[displaydoc("Missing data for key")]
19 MissingDataKey,
20
21 /// There is data for the key, but not for this particular locale.
22 #[displaydoc("Missing data for locale")]
23 MissingLocale,
24
25 /// The request should include a locale.
26 #[displaydoc("Request needs a locale")]
27 NeedsLocale,
28
29 /// The request should not contain a locale.
30 #[displaydoc("Request has an extraneous locale")]
31 ExtraneousLocale,
32
33 /// The resource was blocked by a filter. The resource may or may not be available.
34 #[displaydoc("Resource blocked by filter")]
35 FilteredResource,
36
37 /// The generic type parameter does not match the TypeId. The expected type name is stored
38 /// as context when this error is returned.
39 #[displaydoc("Mismatched types: tried to downcast with {0}, but actual type is different")]
40 MismatchedType(&'static str),
41
42 /// The payload is missing. This is usually caused by a previous error.
43 #[displaydoc("Missing payload")]
44 MissingPayload,
45
46 /// A data provider object was given to an operation in an invalid state.
47 #[displaydoc("Invalid state")]
48 InvalidState,
49
50 /// The syntax of the [`DataKey`] or [`DataLocale`] was invalid.
51 #[displaydoc("Parse error for data key or data locale")]
52 KeyLocaleSyntax,
53
54 /// An unspecified error occurred, such as a Serde error.
55 ///
56 /// Check debug logs for potentially more information.
57 #[displaydoc("Custom")]
58 Custom,
59
60 /// An error occurred while accessing a system resource.
61 #[displaydoc("I/O error: {0:?}")]
62 #[cfg(feature = "std")]
63 Io(std::io::ErrorKind),
64
65 /// An unspecified data source containing the required data is unavailable.
66 #[displaydoc("Missing source data")]
67 #[cfg(feature = "datagen")]
68 MissingSourceData,
69
70 /// An error indicating that the desired buffer format is not available. This usually
71 /// means that a required Cargo feature was not enabled
72 #[displaydoc("Unavailable buffer format: {0:?} (does icu_provider need to be compiled with an additional Cargo feature?)")]
73 UnavailableBufferFormat(BufferFormat),
74}
75
76/// The error type for ICU4X data provider operations.
77///
78/// To create one of these, either start with a [`DataErrorKind`] or use [`DataError::custom()`].
79///
80/// # Example
81///
82/// Create a NeedsLocale error and attach a data request for context:
83///
84/// ```no_run
85/// # use icu_provider::prelude::*;
86/// let key: DataKey = unimplemented!();
87/// let req: DataRequest = unimplemented!();
88/// DataErrorKind::NeedsLocale.with_req(key, req);
89/// ```
90///
91/// Create a named custom error:
92///
93/// ```
94/// # use icu_provider::prelude::*;
95/// DataError::custom("This is an example error");
96/// ```
97#[derive(Clone, Copy, Eq, PartialEq, Debug)]
98#[non_exhaustive]
99pub struct DataError {
100 /// Broad category of the error.
101 pub kind: DataErrorKind,
102
103 /// The data key of the request, if available.
104 pub key: Option<DataKey>,
105
106 /// Additional context, if available.
107 pub str_context: Option<&'static str>,
108
109 /// Whether this error was created in silent mode to not log.
110 pub silent: bool,
111}
112
113impl fmt::Display for DataError {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "ICU4X data error")?;
116 if self.kind != DataErrorKind::Custom {
117 write!(f, ": {}", self.kind)?;
118 }
119 if let Some(key: DataKey) = self.key {
120 write!(f, " (key: {key})")?;
121 }
122 if let Some(str_context: &str) = self.str_context {
123 write!(f, ": {str_context}")?;
124 }
125 Ok(())
126 }
127}
128
129impl DataErrorKind {
130 /// Converts this DataErrorKind into a DataError.
131 ///
132 /// If possible, you should attach context using a `with_` function.
133 #[inline]
134 pub const fn into_error(self) -> DataError {
135 DataError {
136 kind: self,
137 key: None,
138 str_context: None,
139 silent: false,
140 }
141 }
142
143 /// Creates a DataError with a resource key context.
144 #[inline]
145 pub const fn with_key(self, key: DataKey) -> DataError {
146 self.into_error().with_key(key)
147 }
148
149 /// Creates a DataError with a string context.
150 #[inline]
151 pub const fn with_str_context(self, context: &'static str) -> DataError {
152 self.into_error().with_str_context(context)
153 }
154
155 /// Creates a DataError with a type name context.
156 #[inline]
157 pub fn with_type_context<T>(self) -> DataError {
158 self.into_error().with_type_context::<T>()
159 }
160
161 /// Creates a DataError with a request context.
162 #[inline]
163 pub fn with_req(self, key: DataKey, req: DataRequest) -> DataError {
164 self.into_error().with_req(key, req)
165 }
166}
167
168impl DataError {
169 /// Returns a new, empty DataError with kind Custom and a string error message.
170 #[inline]
171 pub const fn custom(str_context: &'static str) -> Self {
172 Self {
173 kind: DataErrorKind::Custom,
174 key: None,
175 str_context: Some(str_context),
176 silent: false,
177 }
178 }
179
180 /// Sets the resource key of a DataError, returning a modified error.
181 #[inline]
182 pub const fn with_key(self, key: DataKey) -> Self {
183 Self {
184 kind: self.kind,
185 key: Some(key),
186 str_context: self.str_context,
187 silent: self.silent,
188 }
189 }
190
191 /// Sets the string context of a DataError, returning a modified error.
192 #[inline]
193 pub const fn with_str_context(self, context: &'static str) -> Self {
194 Self {
195 kind: self.kind,
196 key: self.key,
197 str_context: Some(context),
198 silent: self.silent,
199 }
200 }
201
202 /// Sets the string context of a DataError to the given type name, returning a modified error.
203 #[inline]
204 pub fn with_type_context<T>(self) -> Self {
205 #[cfg(feature = "logging")]
206 if !self.silent {
207 log::warn!("{self}: Type context: {}", core::any::type_name::<T>());
208 }
209 self.with_str_context(core::any::type_name::<T>())
210 }
211
212 /// Logs the data error with the given request, returning an error containing the resource key.
213 ///
214 /// If the "logging" Cargo feature is enabled, this logs the whole request. Either way,
215 /// it returns an error with the resource key portion of the request as context.
216 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
217 pub fn with_req(mut self, key: DataKey, req: DataRequest) -> Self {
218 if req.metadata.silent {
219 self.silent = true;
220 }
221 // Don't write out a log for MissingDataKey since there is no context to add
222 #[cfg(feature = "logging")]
223 if !self.silent && self.kind != DataErrorKind::MissingDataKey {
224 log::warn!("{} (key: {}, request: {})", self, key, req);
225 }
226 self.with_key(key)
227 }
228
229 /// Logs the data error with the given context, then return self.
230 ///
231 /// This does not modify the error, but if the "logging" Cargo feature is enabled,
232 /// it will print out the context.
233 #[cfg(feature = "std")]
234 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
235 pub fn with_path_context<P: AsRef<std::path::Path> + ?Sized>(self, path: &P) -> Self {
236 #[cfg(feature = "logging")]
237 if !self.silent {
238 log::warn!("{} (path: {:?})", self, path.as_ref());
239 }
240 self
241 }
242
243 /// Logs the data error with the given context, then return self.
244 ///
245 /// This does not modify the error, but if the "logging" Cargo feature is enabled,
246 /// it will print out the context.
247 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
248 #[inline]
249 pub fn with_display_context<D: fmt::Display + ?Sized>(self, context: &D) -> Self {
250 #[cfg(feature = "logging")]
251 if !self.silent {
252 log::warn!("{}: {}", self, context);
253 }
254 self
255 }
256
257 /// Logs the data error with the given context, then return self.
258 ///
259 /// This does not modify the error, but if the "logging" Cargo feature is enabled,
260 /// it will print out the context.
261 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
262 #[inline]
263 pub fn with_debug_context<D: fmt::Debug + ?Sized>(self, context: &D) -> Self {
264 #[cfg(feature = "logging")]
265 if !self.silent {
266 log::warn!("{}: {:?}", self, context);
267 }
268 self
269 }
270
271 #[inline]
272 pub(crate) fn for_type<T>() -> DataError {
273 DataError {
274 kind: DataErrorKind::MismatchedType(core::any::type_name::<T>()),
275 key: None,
276 str_context: None,
277 silent: false,
278 }
279 }
280}
281
282#[cfg(feature = "std")]
283impl std::error::Error for DataError {}
284
285#[cfg(feature = "std")]
286impl From<std::io::Error> for DataError {
287 fn from(e: std::io::Error) -> Self {
288 #[cfg(feature = "logging")]
289 log::warn!("I/O error: {}", e);
290 DataErrorKind::Io(e.kind()).into_error()
291 }
292}
293