1#[macro_export]
8macro_rules! handle {
9 ($result:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
10 match $result {
11 Ok(value) => value,
12 Err(source) => return Err($variant {
13 source: source.into(),
14 $($arg: $crate::_into!($arg$(: $value)?)),*
15 }),
16 }
17 };
18}
19
20#[macro_export]
22macro_rules! handle_opt {
23 ($option:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
24 match $option {
25 Some(value) => value,
26 None => return Err($variant {
27 $($arg: $crate::_into!($arg$(: $value)?)),*
28 }),
29 }
30 };
31}
32
33#[macro_export]
38macro_rules! handle_opt_take {
39 ($option:expr, $variant:ident, $some_value:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
40 if let Some($some_value) = $option.take() {
41 return Err($variant {
42 $some_value: $some_value.into(),
43 $($arg: $crate::_into!($arg$(: $value)?)),*
44 })
45 }
46 };
47}
48
49#[macro_export]
53macro_rules! handle_bool {
54 ($condition:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
55 if $condition {
56 return Err($variant {
57 $($arg: $crate::_into!($arg$(: $value)?)),*
58 });
59 };
60 };
61}
62
63#[macro_export]
67macro_rules! handle_iter {
68 ($results:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
69 {
70 match $crate::partition_result($results) {
71 Ok(oks) => oks,
72 Err(errors) => {
73 return Err($variant {
74 source: errors.into(),
75 $($arg: $crate::_into!($arg$(: $value)?)),*
76 });
77 }
78 }
79 }
80 };
81}
82
83#[macro_export]
89macro_rules! handle_iter_of_refs {
90 ($results:expr, $items:expr, $variant:ident $(, $arg:ident$(: $value:expr)?)*) => {
91 {
92 use alloc::vec::Vec;
93 let (outputs, items, errors) = core::iter::zip($results, $items).fold(
94 (Vec::new(), Vec::new(), Vec::new()),
95 |(mut outputs, mut items, mut errors), (result, item)| {
96 match result {
97 Ok(output) => {
98 outputs.push(output);
99 items.push(item);
100 }
101 Err(source) => {
102 errors.push($crate::ItemError {
103 item,
104 source,
105 });
106 }
107 }
108 (outputs, items, errors)
109 },
110 );
111 if errors.is_empty() {
112 (outputs, items)
113 } else {
114 return Err($variant {
115 source: errors.into(),
116 $($arg: $crate::_into!($arg$(: $value)?)),*
117 });
118 }
119 }
120 };
121}
122
123#[macro_export]
125macro_rules! handle_into_iter {
126 ($results:expr, $variant:ident $(, $arg:ident$(: $value:expr)?)*) => {
127 $crate::handle_iter!($results.into_iter(), $variant $(, $arg$(: $value)?),*)
128 };
129}
130
131#[macro_export]
133macro_rules! handle_discard {
134 ($result:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
135 match $result {
136 Ok(value) => value,
137 Err(_) => return Err($variant {
138 $($arg: $crate::_into!($arg$(: $value)?)),*
139 }),
140 }
141 };
142}
143
144#[macro_export]
146macro_rules! map_err {
147 ($result:expr, $variant:ident$(,)? $($arg:ident$(: $value:expr)?),*) => {
148 $result.map_err(|source| $variant {
149 source: source.into(),
150 $($arg: $crate::_into!($arg$(: $value)?)),*
151 })
152 };
153}
154
155#[doc(hidden)]
157#[macro_export]
158macro_rules! _into {
159 ($arg:ident) => {
160 $arg.into()
161 };
162 ($arg:ident: $value:expr) => {
163 $value.into()
164 };
165}
166
167#[doc(hidden)]
169#[macro_export]
170macro_rules! _index_err {
171 ($f:ident) => {
172 |(index, item)| $f(item).map_err(|err| (index, err))
173 };
174}
175
176#[doc(hidden)]
178#[macro_export]
179macro_rules! _index_err_async {
180 ($f:ident) => {
181 async |(index, item)| $f(item).await.map_err(|err| (index, err))
182 };
183}
184
185#[cfg(all(test, feature = "std"))]
186mod tests {
187 use crate::{ErrVec, ItemError, PathBufDisplay};
188 use futures::future::join_all;
189 use serde::{Deserialize, Serialize};
190 use std::io;
191 use std::path::{Path, PathBuf};
192 use std::str::FromStr;
193 use std::sync::{Arc, RwLock};
194 use thiserror::Error;
195 use tokio::fs::read_to_string;
196 use tokio::task::JoinSet;
197
198 #[allow(dead_code)]
199 struct PrintNameCommand {
200 dir: PathBuf,
201 format: Format,
202 }
203
204 #[allow(dead_code)]
205 impl PrintNameCommand {
206 async fn run(self) -> Result<(), PrintNameCommandError> {
207 use PrintNameCommandError::*;
208 let Self {
209 dir,
210 format,
211 } = self;
212 let config = handle!(parse_config(&dir, format).await, ParseConfigFailed);
213 println!("{}", config.name);
214 Ok(())
215 }
216 }
217
218 #[allow(dead_code)]
220 async fn parse_config(dir: &Path, format: Format) -> Result<Config, ParseConfigError> {
221 use Format::*;
222 use ParseConfigError::*;
223 let path_buf = dir.join("config.json");
224 let contents = handle!(read_to_string(&path_buf).await, ReadFileFailed, path: path_buf);
225 match format {
226 Json => {
227 let config = handle!(serde_json::de::from_str(&contents), DeserializeFromJson, path: path_buf, contents);
228 Ok(config)
229 }
230 Toml => {
231 let config = handle!(toml::de::from_str(&contents), DeserializeFromToml, path: path_buf, contents);
232 Ok(config)
233 }
234 }
235 }
236
237 #[allow(dead_code)]
239 fn find_even(numbers: Vec<u32>) -> Result<u32, FindEvenError> {
240 use FindEvenError::*;
241 let even = handle_opt!(numbers.iter().find(|x| *x % 2 == 0), NotFound);
242 Ok(*even)
243 }
244
245 #[allow(dead_code)]
247 fn multiply_evens(numbers: Vec<u32>) -> Result<Vec<u32>, MultiplyEvensError> {
248 use MultiplyEvensError::*;
249 let results = numbers.into_iter().map(|number| {
250 use CheckEvenError::*;
251 if number % 2 == 0 {
252 Ok(number * 10)
253 } else {
254 Err(NumberNotEven {
255 number,
256 })
257 }
258 });
259 Ok(handle_iter!(results, CheckEvensFailed))
260 }
261
262 #[allow(dead_code)]
264 async fn read_files(paths: Vec<PathBuf>) -> Result<Vec<String>, ReadFilesError> {
265 use ReadFilesError::*;
266 let results = paths
267 .into_iter()
268 .map(check_file)
269 .collect::<JoinSet<_>>()
270 .join_all()
271 .await;
272 Ok(handle_into_iter!(results, CheckFilesFailed))
273 }
274
275 #[allow(dead_code)]
276 async fn read_files_ref(paths: Vec<PathBuf>) -> Result<Vec<String>, ReadFilesRefError> {
277 use ReadFilesRefError::*;
278 let iter = paths.iter().map(check_file_ref);
279 let results = join_all(iter).await;
280 let items = paths.into_iter().map(PathBufDisplay::from);
281 let (outputs, _items) = handle_iter_of_refs!(results.into_iter(), items, CheckFilesRefFailed);
282 Ok(outputs)
283 }
284
285 #[allow(dead_code)]
289 async fn process(number: u32) -> Result<u32, ProcessError> {
290 Ok(number)
291 }
292
293 #[derive(Error, Debug)]
294 enum PrintNameCommandError {
295 #[error("failed to parse config")]
296 ParseConfigFailed { source: ParseConfigError },
297 }
298
299 #[derive(Error, Debug)]
303 enum ParseConfigError {
304 #[error("failed to read the file: {path}")]
305 ReadFileFailed { path: PathBuf, source: io::Error },
306 #[error("failed to deserialize the file contents from JSON: {path}")]
307 DeserializeFromJson { path: PathBuf, contents: String, source: Box<serde_json::error::Error> },
308 #[error("failed to deserialize the file contents from TOML: {path}")]
309 DeserializeFromToml { path: PathBuf, contents: String, source: Box<toml::de::Error> },
310 }
311
312 #[allow(dead_code)]
313 #[derive(Error, Debug)]
314 enum ProcessError {}
315
316 #[allow(dead_code)]
317 #[derive(Copy, Clone, Debug)]
318 enum Format {
319 Json,
320 Toml,
321 }
322
323 #[derive(Serialize, Deserialize, Clone, Debug)]
324 struct Config {
325 name: String,
326 timeout: u64,
327 parallel: bool,
328 }
329
330 #[allow(dead_code)]
331 fn parse_even_number(input: &str) -> Result<u32, ParseEvenNumberError> {
332 use ParseEvenNumberError::*;
333 let number = handle!(input.parse::<u32>(), InputParseFailed);
334 handle_bool!(number % 2 != 0, NumberNotEven, number);
335 Ok(number)
336 }
337
338 #[derive(Error, Debug)]
339 enum ParseEvenNumberError {
340 #[error("failed to parse input")]
341 InputParseFailed { source: <u32 as FromStr>::Err },
342 #[error("number is not even: {number}")]
343 NumberNotEven { number: u32 },
344 }
345
346 #[derive(Error, Debug)]
347 enum FindEvenError {
348 #[error("even number not found")]
349 NotFound,
350 }
351
352 #[derive(Error, Debug)]
353 enum MultiplyEvensError {
354 #[error("failed to check {len} numbers", len = source.len())]
355 CheckEvensFailed { source: ErrVec<CheckEvenError> },
356 }
357
358 #[derive(Error, Debug)]
359 enum ReadFilesError {
360 #[error("failed to check {len} files", len = source.len())]
361 CheckFilesFailed { source: ErrVec<CheckFileError> },
362 }
363
364 #[derive(Error, Debug)]
365 enum ReadFilesRefError {
366 #[error("failed to check {len} files", len = source.len())]
367 CheckFilesRefFailed { source: ErrVec<ItemError<PathBufDisplay, CheckFileRefError>> },
368 }
369
370 #[derive(Error, Debug)]
371 enum CheckEvenError {
372 #[error("number is not even: {number}")]
373 NumberNotEven { number: u32 },
374 }
375
376 async fn check_file(path: PathBuf) -> Result<String, CheckFileError> {
377 use CheckFileError::*;
378 let content = handle!(read_to_string(&path).await, ReadToStringFailed, path);
379 handle_bool!(content.is_empty(), FileIsEmpty, path);
380 Ok(content)
381 }
382
383 #[derive(Error, Debug)]
384 enum CheckFileError {
385 #[error("failed to read the file to string: {path}")]
386 ReadToStringFailed { path: PathBuf, source: io::Error },
387 #[error("file is empty: {path}")]
388 FileIsEmpty { path: PathBuf },
389 }
390
391 async fn check_file_ref(path: &PathBuf) -> Result<String, CheckFileRefError> {
392 use CheckFileRefError::*;
393 let content = handle!(read_to_string(&path).await, ReadToStringFailed);
394 handle_bool!(content.is_empty(), FileIsEmpty);
395 Ok(content)
396 }
397
398 #[derive(Error, Debug)]
399 enum CheckFileRefError {
400 #[error("failed to read the file to string")]
401 ReadToStringFailed { source: io::Error },
402 #[error("file is empty")]
403 FileIsEmpty,
404 }
405
406 #[derive(Clone, Debug)]
407 struct Db {
408 user: User,
409 }
410
411 #[derive(Clone, Debug)]
412 struct User {
413 username: String,
414 }
415
416 #[allow(dead_code)]
417 fn get_username(db: Arc<RwLock<Db>>) -> Result<String, GetUsernameError> {
418 use GetUsernameError::*;
419 let guard = handle_discard!(db.read(), AcquireReadLockFailed);
423 let username = guard.user.username.clone();
424 Ok(username)
425 }
426
427 #[derive(Error, Debug)]
428 pub enum GetUsernameError {
429 #[error("failed to acquire read lock")]
430 AcquireReadLockFailed,
431 }
432
433 #[allow(dead_code)]
434 fn get_answer(prompt: String, get_response: &mut impl FnMut(String) -> Result<WeirdResponse, io::Error>) -> Result<String, GetAnswerError> {
435 use GetAnswerError::*;
436 let mut response = handle!(get_response(prompt.clone()), GetResponseFailed, prompt);
439 handle_opt_take!(response.error, ResponseContainsError, error);
440 Ok(response.answer)
441 }
442
443 #[derive(Debug)]
445 pub struct WeirdResponse {
446 answer: String,
447 error: Option<WeirdResponseError>,
448 }
449
450 #[allow(dead_code)]
451 #[derive(Error, Debug)]
452 pub enum WeirdResponseError {
453 #[error("prompt is empty")]
454 PromptIsEmpty,
455 #[error("context limit reached")]
456 ContextLimitReached,
457 }
458
459 #[derive(Error, Debug)]
461 pub enum GetAnswerError {
462 #[error("failed to get response")]
463 GetResponseFailed { source: io::Error, prompt: String },
464 #[error("response contains an error")]
465 ResponseContainsError { error: WeirdResponseError },
466 }
467}