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