1use std::fmt::{self, Display, Formatter};
2
3pub(crate) const MAX_ERROR_MESSAGE_BYTES: usize = 4096;
4
5#[derive(Debug)]
7pub struct MapError {
8 index: usize,
9 message: String,
10}
11
12impl MapError {
13 pub fn index(&self) -> usize {
15 self.index
16 }
17
18 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
54pub 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 {
90 use super::{map, MapError};
91
92 #[test]
93 fn empty_input_succeeds() {
94 let result = map(Vec::<i32>::new(), |_| Ok::<_, &str>(0));
95 assert_eq!(result.unwrap(), Vec::<i32>::new());
96 }
97
98 #[test]
99 fn successful_inputs_are_ordered_and_called_once() {
100 let calls = std::cell::Cell::new(0);
101 let result = map(vec![3, 1, 2], |item| {
102 calls.set(calls.get() + 1);
103 Ok::<_, &str>(item * 2)
104 });
105
106 assert_eq!(result.unwrap(), vec![6, 2, 4]);
107 assert_eq!(calls.get(), 3);
108 }
109
110 #[test]
111 fn first_error_stops_later_callbacks() {
112 let calls = std::cell::Cell::new(0);
113 let result = map(vec![10, 20, 30], |item| {
114 calls.set(calls.get() + 1);
115 if item == 20 {
116 Err::<i32, _>("bad item")
117 } else {
118 Ok(item)
119 }
120 });
121
122 let error = result.unwrap_err();
123 assert_eq!(error.index(), 1);
124 assert_eq!(error.message(), "bad item");
125 assert_eq!(calls.get(), 2);
126 }
127
128 #[test]
129 fn ascii_error_is_capped() {
130 let result = map(vec![()], |_| Err::<(), _>("x".repeat(5000)));
131 assert_eq!(result.unwrap_err().message().len(), 4096);
132 }
133
134 #[test]
135 fn multibyte_error_is_truncated_at_utf8_boundary() {
136 let message = format!("{}é", "a".repeat(4095));
137 let error = map(vec![()], |_| Err::<(), _>(message.clone())).unwrap_err();
138
139 assert_eq!(error.message().len(), 4095);
140 assert!(error.message().is_char_boundary(error.message().len()));
141 }
142
143 #[test]
144 fn serial_map_accepts_borrowed_non_static_values_and_state() {
145 use std::{cell::RefCell, rc::Rc};
146
147 let input_text = String::from("borrowed input");
148 let error_text = String::from("borrowed error");
149 let calls = Rc::new(RefCell::new(0));
150 let captured = Rc::clone(&calls);
151 let input = Rc::new(input_text.as_str());
152 let output: Result<Vec<Rc<&str>>, MapError> = map(vec![input], |item| {
153 *captured.borrow_mut() += 1;
154 Ok::<_, Rc<&str>>(item)
155 });
156
157 assert_eq!(output.unwrap().as_slice(), &[Rc::new("borrowed input")]);
158 assert_eq!(*calls.borrow(), 1);
159
160 let error = map(vec![Rc::new(input_text.as_str())], |_| {
161 Err::<(), _>(Rc::new(error_text.as_str()))
162 })
163 .unwrap_err();
164 assert_eq!(error.message(), "borrowed error");
165 }
166}