Skip to main content

hataori/
map.rs

1use std::fmt::{self, Display, Formatter};
2
3pub(crate) const MAX_ERROR_MESSAGE_BYTES: usize = 4096;
4
5/// The first callback error returned by [`map`].
6#[derive(Debug)]
7pub struct MapError {
8    index: usize,
9    message: String,
10}
11
12impl MapError {
13    /// Returns the zero-based index of the input that failed.
14    pub fn index(&self) -> usize {
15        self.index
16    }
17
18    /// Returns the callback error message, at most 4096 bytes and truncated at a UTF-8 character boundary.
19    pub fn message(&self) -> &str {
20        &self.message
21    }
22
23    fn new<E: Display>(index: usize, error: E) -> Self {
24        Self {
25            index,
26            message: truncate_message(error.to_string()),
27        }
28    }
29}
30
31impl Display for MapError {
32    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
33        write!(
34            formatter,
35            "map failed at index {}: {}",
36            self.index, self.message
37        )
38    }
39}
40
41impl std::error::Error for MapError {}
42
43pub(crate) fn truncate_message(mut message: String) -> String {
44    if message.len() > MAX_ERROR_MESSAGE_BYTES {
45        let mut end = MAX_ERROR_MESSAGE_BYTES;
46        while !message.is_char_boundary(end) {
47            end -= 1;
48        }
49        message.truncate(end);
50    }
51    message
52}
53
54/// Applies `f` to `items` sequentially and returns results in their original order.
55///
56/// Each successful input is evaluated exactly once. Evaluation stops at the first
57/// callback error, and trailing unconsumed items are dropped naturally. Errors
58/// report a zero-based input index and a message truncated at a UTF-8 character
59/// boundary to at most 4096 bytes. This serial operation has no serde, `Send`,
60/// `Sync`, or `'static` requirements.
61///
62/// # Example
63///
64/// ```
65/// use hataori::map;
66///
67/// let doubled = map(vec![1, 2, 3], |item| {
68///     Ok::<_, std::convert::Infallible>(item * 2)
69/// })
70/// .unwrap();
71/// assert_eq!(doubled, vec![2, 4, 6]);
72/// ```
73pub fn map<T, U, E, F>(items: Vec<T>, mut f: F) -> Result<Vec<U>, MapError>
74where
75    F: FnMut(T) -> Result<U, E>,
76    E: Display,
77{
78    let mut output = Vec::with_capacity(items.len());
79    for (index, item) in items.into_iter().enumerate() {
80        match f(item) {
81            Ok(value) => output.push(value),
82            Err(error) => return Err(MapError::new(index, error)),
83        }
84    }
85    Ok(output)
86}
87
88#[cfg(test)]
89mod tests;