• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 super::*;
6 use crate::parts_write_adapter::CoreWriteAsPartsWrite;
7 use core::convert::Infallible;
8 
9 /// A writeable object that can fail while writing.
10 ///
11 /// The default [`Writeable`] trait returns a [`fmt::Error`], which originates from the sink.
12 /// In contrast, this trait allows the _writeable itself_ to trigger an error as well.
13 ///
14 /// Implementations are expected to always make a _best attempt_ at writing to the sink
15 /// and should write replacement values in the error state. Therefore, the returned `Result`
16 /// can be safely ignored to emulate a "lossy" mode.
17 ///
18 /// Any error substrings should be annotated with [`Part::ERROR`].
19 ///
20 /// # Implementer Notes
21 ///
22 /// This trait requires that implementers make a _best attempt_ at writing to the sink,
23 /// _even in the error state_, such as with a placeholder or fallback string.
24 ///
25 /// In [`TryWriteable::try_write_to_parts()`], error substrings should be annotated with
26 /// [`Part::ERROR`]. Because of this, writing to parts is not default-implemented like
27 /// it is on [`Writeable`].
28 ///
29 /// The trait is implemented on [`Result<T, E>`] where `T` and `E` both implement [`Writeable`];
30 /// In the `Ok` case, `T` is written, and in the `Err` case, `E` is written as a fallback value.
31 /// This impl, which writes [`Part::ERROR`], can be used as a basis for more advanced impls.
32 ///
33 /// # Examples
34 ///
35 /// Implementing on a custom type:
36 ///
37 /// ```
38 /// use core::fmt;
39 /// use writeable::LengthHint;
40 /// use writeable::PartsWrite;
41 /// use writeable::TryWriteable;
42 ///
43 /// #[derive(Debug, PartialEq, Eq)]
44 /// enum HelloWorldWriteableError {
45 ///     MissingName,
46 /// }
47 ///
48 /// #[derive(Debug, PartialEq, Eq)]
49 /// struct HelloWorldWriteable {
50 ///     pub name: Option<&'static str>,
51 /// }
52 ///
53 /// impl TryWriteable for HelloWorldWriteable {
54 ///     type Error = HelloWorldWriteableError;
55 ///
56 ///     fn try_write_to_parts<S: PartsWrite + ?Sized>(
57 ///         &self,
58 ///         sink: &mut S,
59 ///     ) -> Result<Result<(), Self::Error>, fmt::Error> {
60 ///         sink.write_str("Hello, ")?;
61 ///         // Use `impl TryWriteable for Result` to generate the error part:
62 ///         let err = self.name.ok_or("nobody").try_write_to_parts(sink)?.err();
63 ///         sink.write_char('!')?;
64 ///         // Return a doubly-wrapped Result.
65 ///         // The outer Result is for fmt::Error, handled by the `?`s above.
66 ///         // The inner Result is for our own Self::Error.
67 ///         if err.is_none() {
68 ///             Ok(Ok(()))
69 ///         } else {
70 ///             Ok(Err(HelloWorldWriteableError::MissingName))
71 ///         }
72 ///     }
73 ///
74 ///     fn writeable_length_hint(&self) -> LengthHint {
75 ///         self.name.ok_or("nobody").writeable_length_hint() + 8
76 ///     }
77 /// }
78 ///
79 /// // Success case:
80 /// writeable::assert_try_writeable_eq!(
81 ///     HelloWorldWriteable {
82 ///         name: Some("Alice")
83 ///     },
84 ///     "Hello, Alice!"
85 /// );
86 ///
87 /// // Failure case, including the ERROR part:
88 /// writeable::assert_try_writeable_parts_eq!(
89 ///     HelloWorldWriteable { name: None },
90 ///     "Hello, nobody!",
91 ///     Err(HelloWorldWriteableError::MissingName),
92 ///     [(7, 13, writeable::Part::ERROR)]
93 /// );
94 /// ```
95 pub trait TryWriteable {
96     type Error;
97 
98     /// Writes the content of this writeable to a sink.
99     ///
100     /// If the sink hits an error, writing immediately ends,
101     /// `Err(`[`fmt::Error`]`)` is returned, and the sink does not contain valid output.
102     ///
103     /// If the writeable hits an error, writing is continued with a replacement value,
104     /// `Ok(Err(`[`TryWriteable::Error`]`))` is returned, and the caller may continue using the sink.
105     ///
106     /// # Lossy Mode
107     ///
108     /// The [`fmt::Error`] should always be handled, but the [`TryWriteable::Error`] can be
109     /// ignored if a fallback string is desired instead of an error.
110     ///
111     /// To handle the sink error, but not the writeable error, write:
112     ///
113     /// ```
114     /// # use writeable::TryWriteable;
115     /// # let my_writeable: Result<&str, &str> = Ok("");
116     /// # let mut sink = String::new();
117     /// let _ = my_writeable.try_write_to(&mut sink)?;
118     /// # Ok::<(), core::fmt::Error>(())
119     /// ```
120     ///
121     /// # Examples
122     ///
123     /// The following examples use `Result<&str, usize>`, which implements [`TryWriteable`] because both `&str` and `usize` do.
124     ///
125     /// Success case:
126     ///
127     /// ```
128     /// use writeable::TryWriteable;
129     ///
130     /// let w: Result<&str, usize> = Ok("success");
131     /// let mut sink = String::new();
132     /// let result = w.try_write_to(&mut sink);
133     ///
134     /// assert_eq!(result, Ok(Ok(())));
135     /// assert_eq!(sink, "success");
136     /// ```
137     ///
138     /// Failure case:
139     ///
140     /// ```
141     /// use writeable::TryWriteable;
142     ///
143     /// let w: Result<&str, usize> = Err(44);
144     /// let mut sink = String::new();
145     /// let result = w.try_write_to(&mut sink);
146     ///
147     /// assert_eq!(result, Ok(Err(44)));
148     /// assert_eq!(sink, "44");
149     /// ```
try_write_to<W: fmt::Write + ?Sized>( &self, sink: &mut W, ) -> Result<Result<(), Self::Error>, fmt::Error>150     fn try_write_to<W: fmt::Write + ?Sized>(
151         &self,
152         sink: &mut W,
153     ) -> Result<Result<(), Self::Error>, fmt::Error> {
154         self.try_write_to_parts(&mut CoreWriteAsPartsWrite(sink))
155     }
156 
157     /// Writes the content of this writeable to a sink with parts (annotations).
158     ///
159     /// For more information, see:
160     ///
161     /// - [`TryWriteable::try_write_to()`] for the general behavior.
162     /// - [`TryWriteable`] for an example with parts.
163     /// - [`Part`] for more about parts.
try_write_to_parts<S: PartsWrite + ?Sized>( &self, sink: &mut S, ) -> Result<Result<(), Self::Error>, fmt::Error>164     fn try_write_to_parts<S: PartsWrite + ?Sized>(
165         &self,
166         sink: &mut S,
167     ) -> Result<Result<(), Self::Error>, fmt::Error>;
168 
169     /// Returns a hint for the number of UTF-8 bytes that will be written to the sink.
170     ///
171     /// This function returns the length of the "lossy mode" string; for more information,
172     /// see [`TryWriteable::try_write_to()`].
writeable_length_hint(&self) -> LengthHint173     fn writeable_length_hint(&self) -> LengthHint {
174         LengthHint::undefined()
175     }
176 
177     /// Writes the content of this writeable to a string.
178     ///
179     /// In the failure case, this function returns the error and the best-effort string ("lossy mode").
180     ///
181     /// Examples
182     ///
183     /// ```
184     /// # use std::borrow::Cow;
185     /// # use writeable::TryWriteable;
186     /// // use the best-effort string
187     /// let r1: Cow<str> = Ok::<&str, u8>("ok")
188     ///     .try_write_to_string()
189     ///     .unwrap_or_else(|(_, s)| s);
190     /// // propagate the error
191     /// let r2: Result<Cow<str>, u8> = Ok::<&str, u8>("ok")
192     ///     .try_write_to_string()
193     ///     .map_err(|(e, _)| e);
194     /// ```
try_write_to_string(&self) -> Result<Cow<str>, (Self::Error, Cow<str>)>195     fn try_write_to_string(&self) -> Result<Cow<str>, (Self::Error, Cow<str>)> {
196         let hint = self.writeable_length_hint();
197         if hint.is_zero() {
198             return Ok(Cow::Borrowed(""));
199         }
200         let mut output = String::with_capacity(hint.capacity());
201         match self
202             .try_write_to(&mut output)
203             .unwrap_or_else(|fmt::Error| Ok(()))
204         {
205             Ok(()) => Ok(Cow::Owned(output)),
206             Err(e) => Err((e, Cow::Owned(output))),
207         }
208     }
209 }
210 
211 impl<T, E> TryWriteable for Result<T, E>
212 where
213     T: Writeable,
214     E: Writeable + Clone,
215 {
216     type Error = E;
217 
218     #[inline]
try_write_to<W: fmt::Write + ?Sized>( &self, sink: &mut W, ) -> Result<Result<(), Self::Error>, fmt::Error>219     fn try_write_to<W: fmt::Write + ?Sized>(
220         &self,
221         sink: &mut W,
222     ) -> Result<Result<(), Self::Error>, fmt::Error> {
223         match self {
224             Ok(t) => t.write_to(sink).map(Ok),
225             Err(e) => e.write_to(sink).map(|()| Err(e.clone())),
226         }
227     }
228 
229     #[inline]
try_write_to_parts<S: PartsWrite + ?Sized>( &self, sink: &mut S, ) -> Result<Result<(), Self::Error>, fmt::Error>230     fn try_write_to_parts<S: PartsWrite + ?Sized>(
231         &self,
232         sink: &mut S,
233     ) -> Result<Result<(), Self::Error>, fmt::Error> {
234         match self {
235             Ok(t) => t.write_to_parts(sink).map(Ok),
236             Err(e) => sink
237                 .with_part(Part::ERROR, |sink| e.write_to_parts(sink))
238                 .map(|()| Err(e.clone())),
239         }
240     }
241 
242     #[inline]
writeable_length_hint(&self) -> LengthHint243     fn writeable_length_hint(&self) -> LengthHint {
244         match self {
245             Ok(t) => t.writeable_length_hint(),
246             Err(e) => e.writeable_length_hint(),
247         }
248     }
249 
250     #[inline]
try_write_to_string(&self) -> Result<Cow<str>, (Self::Error, Cow<str>)>251     fn try_write_to_string(&self) -> Result<Cow<str>, (Self::Error, Cow<str>)> {
252         match self {
253             Ok(t) => Ok(t.write_to_string()),
254             Err(e) => Err((e.clone(), e.write_to_string())),
255         }
256     }
257 }
258 
259 /// A wrapper around [`TryWriteable`] that implements [`Writeable`]
260 /// if [`TryWriteable::Error`] is [`Infallible`].
261 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
262 #[repr(transparent)]
263 #[allow(clippy::exhaustive_structs)] // transparent newtype
264 pub struct TryWriteableInfallibleAsWriteable<T>(pub T);
265 
266 impl<T> Writeable for TryWriteableInfallibleAsWriteable<T>
267 where
268     T: TryWriteable<Error = Infallible>,
269 {
270     #[inline]
write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result271     fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
272         match self.0.try_write_to(sink) {
273             Ok(Ok(())) => Ok(()),
274             Ok(Err(infallible)) => match infallible {},
275             Err(e) => Err(e),
276         }
277     }
278 
279     #[inline]
write_to_parts<S: PartsWrite + ?Sized>(&self, sink: &mut S) -> fmt::Result280     fn write_to_parts<S: PartsWrite + ?Sized>(&self, sink: &mut S) -> fmt::Result {
281         match self.0.try_write_to_parts(sink) {
282             Ok(Ok(())) => Ok(()),
283             Ok(Err(infallible)) => match infallible {},
284             Err(e) => Err(e),
285         }
286     }
287 
288     #[inline]
writeable_length_hint(&self) -> LengthHint289     fn writeable_length_hint(&self) -> LengthHint {
290         self.0.writeable_length_hint()
291     }
292 
293     #[inline]
write_to_string(&self) -> Cow<str>294     fn write_to_string(&self) -> Cow<str> {
295         match self.0.try_write_to_string() {
296             Ok(s) => s,
297             Err((infallible, _)) => match infallible {},
298         }
299     }
300 }
301 
302 impl<T> fmt::Display for TryWriteableInfallibleAsWriteable<T>
303 where
304     T: TryWriteable<Error = Infallible>,
305 {
306     #[inline]
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result307     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308         self.write_to(f)
309     }
310 }
311 
312 /// A wrapper around [`Writeable`] that implements [`TryWriteable`]
313 /// with [`TryWriteable::Error`] set to [`Infallible`].
314 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
315 #[repr(transparent)]
316 #[allow(clippy::exhaustive_structs)] // transparent newtype
317 pub struct WriteableAsTryWriteableInfallible<T>(pub T);
318 
319 impl<T> TryWriteable for WriteableAsTryWriteableInfallible<T>
320 where
321     T: Writeable,
322 {
323     type Error = Infallible;
324 
325     #[inline]
try_write_to<W: fmt::Write + ?Sized>( &self, sink: &mut W, ) -> Result<Result<(), Infallible>, fmt::Error>326     fn try_write_to<W: fmt::Write + ?Sized>(
327         &self,
328         sink: &mut W,
329     ) -> Result<Result<(), Infallible>, fmt::Error> {
330         self.0.write_to(sink).map(Ok)
331     }
332 
333     #[inline]
try_write_to_parts<S: PartsWrite + ?Sized>( &self, sink: &mut S, ) -> Result<Result<(), Infallible>, fmt::Error>334     fn try_write_to_parts<S: PartsWrite + ?Sized>(
335         &self,
336         sink: &mut S,
337     ) -> Result<Result<(), Infallible>, fmt::Error> {
338         self.0.write_to_parts(sink).map(Ok)
339     }
340 
341     #[inline]
writeable_length_hint(&self) -> LengthHint342     fn writeable_length_hint(&self) -> LengthHint {
343         self.0.writeable_length_hint()
344     }
345 
346     #[inline]
try_write_to_string(&self) -> Result<Cow<str>, (Infallible, Cow<str>)>347     fn try_write_to_string(&self) -> Result<Cow<str>, (Infallible, Cow<str>)> {
348         Ok(self.0.write_to_string())
349     }
350 }
351 
352 /// Testing macros for types implementing [`TryWriteable`].
353 ///
354 /// Arguments, in order:
355 ///
356 /// 1. The [`TryWriteable`] under test
357 /// 2. The expected string value
358 /// 3. The expected result value, or `Ok(())` if omitted
359 /// 3. [`*_parts_eq`] only: a list of parts (`[(start, end, Part)]`)
360 ///
361 /// Any remaining arguments get passed to `format!`
362 ///
363 /// The macros tests the following:
364 ///
365 /// - Equality of string content
366 /// - Equality of parts ([`*_parts_eq`] only)
367 /// - Validity of size hint
368 ///
369 /// For a usage example, see [`TryWriteable`].
370 ///
371 /// [`*_parts_eq`]: assert_try_writeable_parts_eq
372 #[macro_export]
373 macro_rules! assert_try_writeable_eq {
374     ($actual_writeable:expr, $expected_str:expr $(,)?) => {
375         $crate::assert_try_writeable_eq!($actual_writeable, $expected_str, Ok(()))
376     };
377     ($actual_writeable:expr, $expected_str:expr, $expected_result:expr $(,)?) => {
378         $crate::assert_try_writeable_eq!($actual_writeable, $expected_str, $expected_result, "")
379     };
380     ($actual_writeable:expr, $expected_str:expr, $expected_result:expr, $($arg:tt)+) => {{
381         $crate::assert_try_writeable_eq!(@internal, $actual_writeable, $expected_str, $expected_result, $($arg)*);
382     }};
383     (@internal, $actual_writeable:expr, $expected_str:expr, $expected_result:expr, $($arg:tt)+) => {{
384         use $crate::TryWriteable;
385         let actual_writeable = &$actual_writeable;
386         let (actual_str, actual_parts, actual_error) = $crate::_internal::try_writeable_to_parts_for_test(actual_writeable);
387         assert_eq!(actual_str, $expected_str, $($arg)*);
388         assert_eq!(actual_error, Result::<(), _>::from($expected_result).err(), $($arg)*);
389         let actual_result = match actual_writeable.try_write_to_string() {
390             Ok(actual_cow_str) => {
391                 assert_eq!(actual_cow_str, $expected_str, $($arg)+);
392                 Ok(())
393             }
394             Err((e, actual_cow_str)) => {
395                 assert_eq!(actual_cow_str, $expected_str, $($arg)+);
396                 Err(e)
397             }
398         };
399         assert_eq!(actual_result, Result::<(), _>::from($expected_result), $($arg)*);
400         let length_hint = actual_writeable.writeable_length_hint();
401         assert!(
402             length_hint.0 <= actual_str.len(),
403             "hint lower bound {} larger than actual length {}: {}",
404             length_hint.0, actual_str.len(), format!($($arg)*),
405         );
406         if let Some(upper) = length_hint.1 {
407             assert!(
408                 actual_str.len() <= upper,
409                 "hint upper bound {} smaller than actual length {}: {}",
410                 length_hint.0, actual_str.len(), format!($($arg)*),
411             );
412         }
413         actual_parts // return for assert_try_writeable_parts_eq
414     }};
415 }
416 
417 /// See [`assert_try_writeable_eq`].
418 #[macro_export]
419 macro_rules! assert_try_writeable_parts_eq {
420     ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr $(,)?) => {
421         $crate::assert_try_writeable_parts_eq!($actual_writeable, $expected_str, Ok(()), $expected_parts)
422     };
423     ($actual_writeable:expr, $expected_str:expr, $expected_result:expr, $expected_parts:expr $(,)?) => {
424         $crate::assert_try_writeable_parts_eq!($actual_writeable, $expected_str, $expected_result, $expected_parts, "")
425     };
426     ($actual_writeable:expr, $expected_str:expr, $expected_result:expr, $expected_parts:expr, $($arg:tt)+) => {{
427         let actual_parts = $crate::assert_try_writeable_eq!(@internal, $actual_writeable, $expected_str, $expected_result, $($arg)*);
428         assert_eq!(actual_parts, $expected_parts, $($arg)+);
429     }};
430 }
431 
432 #[test]
test_result_try_writeable()433 fn test_result_try_writeable() {
434     let mut result: Result<&str, usize> = Ok("success");
435     assert_try_writeable_eq!(result, "success");
436     result = Err(44);
437     assert_try_writeable_eq!(result, "44", Err(44));
438     assert_try_writeable_parts_eq!(result, "44", Err(44), [(0, 2, Part::ERROR)])
439 }
440