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 | use crate::buf::BufferFormat; |
6 | use crate::prelude::*; |
7 | use core::fmt; |
8 | use 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 ] |
16 | pub 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 ] |
99 | pub 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 | |
113 | impl 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 | |
129 | impl 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 | |
168 | impl 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" )] |
283 | impl std::error::Error for DataError {} |
284 | |
285 | #[cfg (feature = "std" )] |
286 | impl 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 | |